Skip to content

Rfc: nested classes #5

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open

Rfc: nested classes #5

wants to merge 26 commits into from

Conversation

withinboredom
Copy link

@withinboredom withinboredom commented Mar 26, 2025

TODO:

  • tests for use at top of file
  • tests for use in traits

Summary by CodeRabbit

  • New Features

    • Enhanced support for nested and inner classes, including nested enums.
    • Improved Reflection API methods to inspect inner, private, protected, and public class attributes.
    • Upgraded namespace management and autoload functionality.
  • Bug Fixes

    • Refined error messages for invalid class declarations and visibility conflicts.
    • Stricter type-checking and return type validations for class instantiation.
  • Tests

    • Extensive new tests covering nested class behavior, scope resolution, and access modifiers.

Copy link

coderabbitai bot commented Mar 26, 2025

Walkthrough

This pull request introduces broad changes to support nested and inner classes in the Zend Engine. New members are added to the class entry structures to manage required and lexical scopes. The changes update enumeration and grammar definitions within the parser and compile phases to recognize nested classes and adjust modifiers. Namespace management is enhanced through new source files and build configuration updates. Additionally, extensive new tests cover inner classes, access modifiers, return types, autoloading, instantiation, resolution, enums, and reflection, while several functions and error handling paths are updated for improved type and visibility checks.

Changes

File(s) Change Summary
Zend/zend.h Added three new members (required_scope, lexical_scope, required_scope_absolute) to the _zend_class_entry structure.
Zend/zend_compile.h
Zend/zend_compile.c
Added new enum entries (ZEND_MODIFIER_TARGET_NESTED_CLASS, support for ZEND_MODIFIER_TARGET_INNER_CLASS), a new macro (ZEND_NAMESPACE_CLASS), and updated modifier functions with extra methods for inner class handling.
Zend/zend_language_parser.y Introduced new grammar rules and types (nested_class_modifiers, nested_class_statement) for parsing nested class declarations; updated related %type declarations and rules.
Zend/zend_globals.h Added new global members (nested_class_queue, namespaces, global_namespace) to the compiler and executor globals.
Zend/zend_namespaces.c
Zend/zend_namespaces.h
configure.ac
win32/build/config.w32
Created new files and updated build scripts to implement and include namespace management functionality.
Zend/zend_execute_API.c Initialized and cleaned up new namespace globals (EG(namespaces), EG(global_namespace)) during executor lifecycle.
Zend/zend_opcode.c
ext/opcache/zend_persist.c
Modified destruction logic for namespace classes and added zend_update_ce_scopes to update scope properties during persistence.
Zend/zend_object_handlers.c Enhanced property and method access with new lexical scope checks and error handling.
ext/reflection/php_reflection.c
ext/reflection/php_reflection.stub.php
ext/reflection/php_reflection_arginfo.h
ext/reflection/tests/ReflectionClass_toString_001.phpt
Added four new reflection methods (isPrivate, isProtected, isPublic, isInnerClass) expanding introspection capabilities on class visibility.
Zend/zend_execute.c
Zend/Optimizer/dfa_pass.c
Zend/zend_API.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
Introduced new functions and enhanced error handling for verifying return types and property types against class scopes and visibility rules.
Tests (multiple files under tests/classes/inner_classes/ and others) Added over fifty new test files to validate nested/inner class declarations, modifiers (access, static, readonly), inheritance, autoloading, resolution, instantiation, enum and interface usage, and visibility/return type constraints.
Other test files (Zend/tests/errmsg/errmsg_027.phpt, ext/reflection/tests/bug74454.phpt) Updated expected error messages to reflect new restrictions regarding nested class declarations.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Instantiator as _object_and_properties_init
  participant ScopeChecker as zend_check_type_visibility
  participant ClassEntry
  Caller->>Instantiator: Request instance creation
  Instantiator->>ClassEntry: Check required_scope/lexical_scope
  alt Scope not satisfied
      ClassEntry-->>Instantiator: Return type error message
      Instantiator-->>Caller: Error thrown
  else Scope valid
      Instantiator->>ScopeChecker: Verify property and return type visibility
      ScopeChecker-->>Instantiator: Visibility OK
      Instantiator-->>Caller: Return instance
  end
Loading
sequenceDiagram
  participant Parser
  participant NamespaceManager
  participant GlobalTable
  Parser->>NamespaceManager: Call zend_resolve_namespace(name)
  NamespaceManager->>GlobalTable: Lookup namespace entry
  alt Namespace exists
      GlobalTable-->>NamespaceManager: Return existing namespace
  else Namespace missing
      NamespaceManager->>GlobalTable: Create and insert new namespace
  end
  NamespaceManager-->>Parser: Return namespace entry
Loading

Poem

I'm a little rabbit on a coding spree,
Hoping through nested classes so merrily.
With scopes and namespaces now shining bright,
I bounce through errors and tests each night.
In the Zend world, my code now sings—
A joyful leap of refactored things!
🐇💻✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
Zend/zend.h (1)

167-170: Appropriate structure additions to support nested classes.

These three new fields add the necessary infrastructure to implement inner classes:

  • required_scope: Points to the parent/outer class that contains this class
  • lexical_scope: Handles the lexical scope for proper name resolution
  • required_scope_absolute: Determines whether scope references are absolute

Consider adding comments to explain the purpose of these fields, particularly what required_scope_absolute controls, to improve code maintainability for future developers.

+	/* Class scope information for nested classes */
 	zend_class_entry *required_scope;
 	zend_class_entry *lexical_scope;
+	/* Whether the required scope is an absolute reference */
 	bool required_scope_absolute;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce79eb and d139c38.

📒 Files selected for processing (4)
  • Zend/zend.h (1 hunks)
  • Zend/zend_compile.h (1 hunks)
  • Zend/zend_language_parser.y (4 hunks)
  • tests/classes/inner_classes/simple_declaration.phpt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: BENCHMARKING
🔇 Additional comments (7)
tests/classes/inner_classes/simple_declaration.phpt (1)

1-22: Well-structured test for nested class declarations.

This test correctly validates the basic functionality of nested class declarations by checking both string-based class name lookups and the ::class constant syntax. It confirms that PHP properly recognizes the nested class hierarchy using namespace-like syntax.

A few considerations for future enhancements:

  • Adding tests for accessing nested classes from within their parent classes
  • Testing visibility modifiers (public, private, protected) on nested classes
  • Verifying inheritance and interfaces with nested classes
Zend/zend_compile.h (1)

897-897: Correct extension of modifier targets for inner classes.

Adding ZEND_MODIFIER_TARGET_INNER_CLASS to the enum is a logical extension that enables specifying modifiers for inner classes. The placement at the end of the enumeration maintains backward compatibility with existing code.

This change integrates well with the other modifications to support the nested classes feature.

Zend/zend_language_parser.y (5)

288-288: Verify that adding inner_class_statement as a grammar type does not introduce new parser conflicts.
This new type definition looks appropriate, but please re-run the parser generator to confirm that no shift/reduce or reduce/reduce conflicts were introduced and that %expect 0 remains valid.

Would you like a script to automate checking for bison parser conflicts?


291-291: inner_class_modifiers declaration is consistent with existing patterns.
Defining inner_class_modifiers as <num> alongside other modifiers is coherent and follows the established approach in this grammar file.


631-638: Confirm defaulting to ZEND_ACC_PUBLIC for inner classes.
Assigning ZEND_ACC_PUBLIC when no explicit modifiers are provided might be intentional, but please verify it aligns with the intended visibility rules for nested classes. Additionally, confirm that zend_modifier_list_to_flags() handles invalid or conflicting modifiers (e.g., abstract|final) for inner classes.


954-957: Implementation of inner_class_statement appears consistent.
The new rule parallels top-level class syntax and attaches the same AST structure (ZEND_AST_CLASS). Ensure that downstream code correctly interprets these AST nodes as nested classes during compilation and runtime.


977-977: Attaching modifiers to an inner class.
This snippet correctly applies the gathered inner_class_modifiers to the newly created inner_class_statement. The approach mirrors how other class modifiers are fed into the AST.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
Zend/zend_compile.c (1)

947-948: Align naming convention for “nested class.”

“ZEND_MODIFIER_TARGET_INNER_CLASS” and “nested class” seem inconsistent. Consider using the same terminology to reduce confusion:

} else if (target == ZEND_MODIFIER_TARGET_INNER_CLASS) {
-    member = "nested class";
+    member = "inner class";
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d139c38 and 43eabe4.

📒 Files selected for processing (1)
  • Zend/zend_compile.c (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: BENCHMARKING
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
🔇 Additional comments (2)
Zend/zend_compile.c (2)

1056-1086:

❓ Verification inconclusive

Verify that restricted modifiers for inner classes are covered by tests.

The code disallows multiple access modifiers, static, and explicit public/protected/private sets for inner classes. This is fine if intentional, but verify that these new error paths have corresponding negative tests to ensure consistent enforcement.


🏁 Script executed:

#!/bin/bash
# Search for test coverage of new error messages regarding invalid inner class modifiers.
rg -A 3 "Multiple access type modifiers are not allowed" tests/
rg -A 3 "Static inner classes are not allowed" tests/
rg -A 3 "inner classes are not allowed" tests/

Length of output: 170


Action: Verify Negative Test Coverage for Inner Class Modifiers
The grepped search did not return any tests with the expected error messages ("Multiple access type modifiers are not allowed", "Static inner classes are not allowed", and related messages). This suggests that the negative test cases for these new error paths may be missing or not using these exact strings. Please manually verify that:

  • Negative tests exist that trigger these error conditions.
  • The tests capture the proper error messages when inner class modifiers are misused.

If such tests are absent, adding them would ensure consistent enforcement of these restrictions.


896-913:

❓ Verification inconclusive

Confirm support for these modifiers on inner classes is tested.

Allowing T_READONLY, T_ABSTRACT, and T_FINAL for ZEND_MODIFIER_TARGET_INNER_CLASS is a significant language change. Ensure there are corresponding tests verifying how the engine handles these modifiers on inner classes. Also confirm this logic aligns with language specs (e.g. whether readonly for classes is permitted).

Please run a search in your PHP test files (PHPT, etc.) to confirm that all relevant modifier combinations are adequately tested:


🏁 Script executed:

#!/bin/bash
# Searching for usage of T_READONLY, T_ABSTRACT, and T_FINAL with inner/nested classes
rg -A 5 "class.*readonly" tests/
rg -A 5 "class.*abstract" tests/
rg -A 5 "class.*final" tests/

Length of output: 1461


Action: Verify that inner class modifiers are adequately tested

The search output shows several PHPT tests involving abstract classes, but it does not clearly reveal tests for inner classes using T_READONLY or T_FINAL. Since allowing T_READONLY, T_ABSTRACT, and T_FINAL for ZEND_MODIFIER_TARGET_INNER_CLASS is a significant language change, please ensure that:

  • There are dedicated tests confirming the engine’s behavior when inner classes use the readonly and final modifiers.
  • The existing abstract-related tests are either applicable to inner classes or are supplemented with specific tests for inner classes.
  • This logic fully complies with language specifications (e.g. verify whether readonly is permitted on inner classes).

If the existing test suite does not cover these combinations, please add appropriate tests and confirm their correctness manually.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
Zend/zend_namespaces.c (1)

47-54: LGTM, but consider handling allocation failures

The namespace resolution logic looks correct, but there's no error handling for memory allocation failures. Consider adding checks after memory allocation operations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43eabe4 and a204a04.

📒 Files selected for processing (6)
  • Zend/zend_compile.h (2 hunks)
  • Zend/zend_globals.h (1 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_namespaces.h (1 hunks)
  • configure.ac (1 hunks)
  • win32/build/config.w32 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Zend/zend_compile.h
🧰 Additional context used
🧬 Code Definitions (1)
Zend/zend_namespaces.h (1)
Zend/zend_compile.h (1)
  • name (988-988)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: BENCHMARKING
🔇 Additional comments (7)
Zend/zend_namespaces.h (1)

1-28: Looks good - new header file properly structured.

The header file is well organized with standard licensing information, appropriate header guards, and includes the necessary dependency. The two API functions are declared correctly with the ZEND_API marker, consistent naming conventions, and clear return types.

configure.ac (1)

1760-1760: Correctly added new source file to build system.

The zend_namespaces.c file has been properly added to the Zend module source files list, ensuring it will be compiled as part of the PHP build process.

win32/build/config.w32 (1)

243-243: Correctly added new source file to Windows build configuration.

The new zend_namespaces.c file has been properly added to the sources list for the Windows build, maintaining build consistency across platforms.

Zend/zend_globals.h (1)

194-195: Appropriately added namespace support to executor globals.

The two new fields added to the _zend_executor_globals structure are well-placed:

  1. HashTable *namespaces - For storing namespace information
  2. zend_class_entry *global_namespace - For referencing the global namespace

These additions follow the pattern used for other symbol tables (like function_table, class_table) in the executor globals, which is appropriate for the nested classes feature.

Zend/zend_namespaces.c (3)

77-78: Verify case handling in namespace resolution

The code converts namespace names to lowercase for lookup, which seems appropriate for PHP's case-insensitive namespace handling. Just confirming this aligns with PHP's namespace case sensitivity rules.


89-95: LGTM: The lookup function is properly separated from resolution

Good separation of concerns by having a separate lookup function that doesn't create namespaces. This follows the principle of least surprise.


65-71:

❓ Verification inconclusive

Consider adding cleanup for namespaces hash table

The global namespace and hash table are initialized here, but there's no visible cleanup code. Ensure there's a corresponding function that handles proper destruction of this hash table to prevent memory leaks.


🏁 Script executed:

#!/bin/bash
# Look for cleanup code for the namespaces hash table
rg -A 5 "EG\(namespaces\)" --type c

Length of output: 1624


Action: Verify Global Cleanup Routine for Namespaces

The initialization of the global namespaces hash table in Zend/zend_namespaces.c (lines 65–71) doesn’t include a local cleanup call. However, it appears that the engine may rely on a centralized shutdown (e.g. within the executor globals cleanup routine) to destroy EG(namespaces). Please verify that the executor shutdown sequence (such as in functions like zend_destroy_executor_globals or its equivalent) properly calls zend_hash_destroy on EG(namespaces) so that all elements are freed via ZEND_CLASS_DTOR. If this centralized cleanup isn’t in place, you’ll need to add an explicit destruction routine to prevent memory leaks.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
Zend/zend_namespaces.c (2)

24-25: ⚠️ Potential issue

Fix memory allocation size in create_namespace

There's a memory allocation issue in the create_namespace function. The current code allocates the size of a pointer to a zend_class_entry, not the size of the structure itself.

-zend_class_entry *ns = pemalloc(sizeof(zend_class_entry *), 0);
+zend_class_entry *ns = pemalloc(sizeof(zend_class_entry), 0);

42-44: ⚠️ Potential issue

Potential off-by-one error in namespace parsing

The loop condition while (pos <= end) could lead to reading one byte past the end of the string. Since end points to the null terminator, the condition should be while (pos < end).

-while (pos <= end) {
-   if (pos == end || *pos == '\\') {
+while (pos < end) {
+   if (*pos == '\\') {
🧹 Nitpick comments (4)
Zend/zend_compile.c (2)

896-896: Enable new modifiers for nested classes.

Allowing readonly, abstract, and final on nested classes appears consistent with the new ZEND_MODIFIER_TARGET_NESTED_CLASS target, but please confirm that these modifiers align with your final language design for inner/nested classes. Historically, readonly was primarily intended for properties, so ensure that supporting it at the class level is deliberate and properly tested.

You might add explicit checks or comments clarifying the exact behavior and expected usage of readonly when applied to nested classes, in order to maintain clarity and avoid confusion.

Also applies to: 901-901, 909-910


947-948: Verify naming consistency for “nested class.”

Here, the string is set to "nested class" for display or error messages. However, the PR objectives and codebase references sometimes use the term "inner class." Ensure that the final naming convention is consistently applied across the engine and documentation to reduce confusion among developers.

Zend/zend_namespaces.c (2)

20-22: Improve header inclusion order and add missing headers

Consider organizing includes in a consistent order (system headers first, then Zend headers) and include any missing headers that might be needed.

#include "zend_namespaces.h"
+#include <stdlib.h>
+#include "zend.h"
#include "zend_API.h"
#include "zend_hash.h"

65-71: Add initialization check for EG(namespaces)

Before using zend_hash_init(), you should check if EG(namespaces) is already initialized to avoid potential double initialization.

if (EG(global_namespace) == NULL) {
   EG(global_namespace) = create_namespace(zend_empty_string);
   EG(global_namespace)->lexical_scope = NULL;
+  if (!EG(namespaces)) {
      zend_hash_init(EG(namespaces), 8, NULL, ZEND_CLASS_DTOR, 0);
+  }
   zend_hash_add_ptr(EG(namespaces), zend_empty_string, EG(global_namespace));
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a204a04 and 5f0f77d.

📒 Files selected for processing (8)
  • Zend/zend_compile.c (3 hunks)
  • Zend/zend_compile.h (2 hunks)
  • Zend/zend_globals.h (1 hunks)
  • Zend/zend_language_parser.y (4 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_namespaces.h (1 hunks)
  • configure.ac (1 hunks)
  • win32/build/config.w32 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • configure.ac
  • Zend/zend_namespaces.h
  • Zend/zend_compile.h
  • Zend/zend_globals.h
  • win32/build/config.w32
  • Zend/zend_language_parser.y
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
  • GitHub Check: LINUX_X32_DEBUG_ZTS
🔇 Additional comments (1)
Zend/zend_compile.c (1)

1056-1086:

❓ Verification inconclusive

Validate disallowed modifiers for nested classes.

These new checks prohibit any access (PPP) and static modifiers on nested classes, which could be a significant design choice. Confirm that such restrictions match the intended capabilities of nested/inner classes. Also, consider systematically testing error paths (e.g., “static inner class,” “public(set) inner class,” etc.) to verify that these compilation errors are triggered correctly.


🏁 Script executed:

#!/bin/bash
# Description: Search for references to static/public/protected/private usage in nested classes
# to ensure coverage in tests and confirm errors are triggered as designed.
rg -A 3 'class\s+\w+\s+(static|public|protected|private)' tests/

Length of output: 66


I'll generate a new shell script to search for the specific compile error messages thrown by the checks (e.g., "Static inner classes are not allowed", "Multiple access type modifiers are not allowed", etc.) within the tests directory to help verify that tests are in place for these error conditions.


🏁 Script executed:

#!/bin/bash
# Description: Search for specific compile error messages related to nested classes in tests
rg -n "Static inner classes are not allowed" tests/
rg -n "Multiple access type modifiers are not allowed" tests/
rg -n "Public(set) inner classes are not allowed" tests/
rg -n "Protected(set) inner classes are not allowed" tests/
rg -n "Private(set) inner classes are not allowed" tests/

Length of output: 298


Attention: Verify Nested Class Modifier Restrictions & Test Coverage

The updated checks in Zend/zend_compile.c correctly enforce restrictions for nested classes by disallowing multiple access type modifiers, static, and various access_set modifiers. However, our automated searches in the tests directory did not detect any test cases targeting these specific error messages. Please ensure that you:

  • Add or verify unit tests that deliberately trigger these compile error paths (e.g., scenarios for "Static inner classes are not allowed," "Multiple access type modifiers are not allowed," etc.).
  • Manually confirm that these error conditions occur as expected when the disallowed modifiers are applied on nested classes.

Comment on lines 45 to 48
zend_string *needle = zend_string_init(ZSTR_VAL(name), len, 0);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect namespace segment extraction

The string initialization is using ZSTR_VAL(name) (start of the full name) instead of start (beginning of the current segment). This results in creating namespace prefixes instead of individual segments.

-zend_string *needle = zend_string_init(ZSTR_VAL(name), len, 0);
+zend_string *needle = zend_string_init(start, len, 0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zend_string *needle = zend_string_init(ZSTR_VAL(name), len, 0);
zend_string *needle = zend_string_init(start, len, 0);

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/classes/inner_classes/simple_declaration_004.phpt (1)

33-38: Consider making the expected output more robust.

The expected output specifies object IDs (#1) which might be brittle if PHP's internal object ID assignment changes. Consider either:

  1. Using a more flexible comparison that ignores object IDs
  2. Adding a comment explaining that object IDs might vary in different PHP versions/implementations

This would make the test more resilient to implementation changes.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4354c5 and 24cd8c2.

📒 Files selected for processing (7)
  • Zend/tests/errmsg/errmsg_027.phpt (1 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • ext/reflection/tests/bug74454.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_001.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_002.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_003.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_004.phpt (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
Zend/zend_namespaces.c (4)
Zend/zend_namespaces.h (3)
  • name (25-25)
  • name (26-26)
  • zend_destroy_namespaces (27-27)
Zend/zend_compile.h (2)
  • name (988-988)
  • zend_initialize_class_data (983-983)
Zend/zend_execute_API.c (4)
  • zend_class_entry (714-726)
  • zend_class_entry (1174-1295)
  • zend_class_entry (1298-1301)
  • zend_class_entry (1304-1319)
Zend/zend_compile.c (1)
  • zend_class_entry (1406-1446)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: BENCHMARKING
  • GitHub Check: FREEBSD
🔇 Additional comments (13)
Zend/tests/errmsg/errmsg_027.phpt (1)

16-16: Error message clarified

The error message has been updated to be more specific, correctly indicating that class declarations are not allowed inside functions rather than using the vaguer "may not be nested" language. This change provides better clarity to developers.

tests/classes/inner_classes/simple_declaration_001.phpt (1)

1-22: Well-structured test for nested class functionality

This test appropriately validates the basic functionality of nested classes by checking their existence through both string class names and the ::class constant syntax. The test structure is clean and the assertions are comprehensive.

ext/reflection/tests/bug74454.phpt (1)

17-17: Updated error message for consistency

The change updates the expected parser error message to match the new syntax expectations, changing from "expecting function" to "expecting class". This keeps the test consistent with the updated error messaging in the parser.

tests/classes/inner_classes/simple_declaration_002.phpt (1)

1-14: Appropriate test for function restrictions

This test correctly verifies that classes still cannot be declared inside functions, maintaining an important language restriction. The expected error message matches the updated message in errmsg_027.phpt, demonstrating consistency across error messages.

tests/classes/inner_classes/simple_declaration_003.phpt (1)

1-21: Syntax and functionality verification for nested classes looks good.

This test case correctly validates the basic functionality of nested classes, verifying:

  1. Proper class nesting (Outer > Middle > Inner)
  2. Correct namespace resolution (Foo\Outer\Middle\Inner)
  3. Ability to instantiate and access nested classes via their fully qualified name

The test structure follows the standard PHPT format, with clear test purpose, code, and expected output.

tests/classes/inner_classes/simple_declaration_004.phpt (2)

6-25: Class declaration and inheritance structure is well-designed.

The test correctly defines the scope resolution hierarchy with:

  1. Base Outer class with a public inner Middle class
  2. Outer2 extending Outer and redefining its own Middle class that extends Outer\Middle
  3. Appropriate static methods to test different scope resolution scenarios

The public class syntax appears to be the new modifier syntax being introduced for nested classes.


27-29: Test cases validate the correct scope resolution behaviors.

These three test cases demonstrate different ways to access nested classes:

  1. Accessing inner class from its containing class
  2. Accessing parent's inner class from a child class
  3. Accessing a child's redefined inner class

This comprehensively tests the scope resolution functionality.

Zend/zend_namespaces.c (6)

24-32: Function implementation looks good.

The create_namespace function correctly allocates memory for a new namespace entry, initializes it properly, and sets appropriate flags. The memory management with zend_string_copy for the name field is done correctly.


45-46: Incorrect namespace segment extraction

The string initialization is using ZSTR_VAL(name) (start of the full name) instead of start (beginning of the current segment). This results in creating namespace prefixes instead of individual segments.

-zend_string *needle = zend_string_init(ZSTR_VAL(name), len, 0);
+zend_string *needle = zend_string_init(start, len, 0);

56-58: Update start pointer after segment processing

After processing a namespace segment, the start pointer should be updated to point to the beginning of the next segment.

         zend_string_release(needle);

         parent_ns = ns;
+        start = pos + 1;
      }
      pos ++;

65-88: Good implementation of namespace resolution logic.

The zend_resolve_namespace function correctly handles initialization of the global namespace and hash table when necessary. It has proper checks for NULL or empty names, and efficiently manages memory with appropriate string release after use.


90-96: Add NULL check in zend_lookup_namespace

The function should check if name is NULL to avoid potential crashes. This would also make it consistent with zend_resolve_namespace.

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+      return NULL; // or return EG(global_namespace) for consistency
+   }
    zend_string *lc_name = zend_string_tolower(name);
    zend_class_entry *ns = zend_hash_find_ptr(EG(namespaces), lc_name);
    zend_string_release(lc_name);

98-109: Clean resource management in destruction function.

The zend_destroy_namespaces function correctly handles the cleanup of resources, with proper NULL checks before attempting to free memory or destroy hash tables.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
Zend/zend_namespaces.c (2)

24-32: ⚠️ Potential issue

Memory allocation issue in create_namespace function

The function uses malloc() instead of the appropriate Zend memory allocation function. Since this is creating a persistent structure, you should use pemalloc() with the appropriate persistence flag.

-zend_class_entry *ns = malloc(sizeof(zend_class_entry));
+zend_class_entry *ns = pemalloc(sizeof(zend_class_entry), 0);

34-63: ⚠️ Potential issue

Multiple issues in insert_namespace function

There are several issues with the namespace traversal logic:

  1. The loop condition while (pos <= end) can read past the end of the string
  2. The string segment extraction uses the wrong starting position
  3. The start pointer isn't updated after processing a segment, causing incorrect segmentation

Apply these fixes:

-while (pos <= end) {
-   if (pos == end || *pos == '\\') {
+while (pos < end) {
+   if (*pos == '\\') {
        len = pos - start;
-       zend_string *needle = zend_string_init(ZSTR_VAL(name), len, 0);
+       zend_string *needle = zend_string_init(start, len, 0);

        ns = zend_hash_find_ptr(EG(namespaces), needle);

        if (!ns) {
           zend_string *interned_name = zend_new_interned_string(needle);
           ns = create_namespace(interned_name);
           ns->lexical_scope = parent_ns;
           zend_hash_add_ptr(EG(namespaces), interned_name, ns);
        }
        zend_string_release(needle);

        parent_ns = ns;
+       start = pos + 1;
     }
     pos ++;
}

Also, consider handling the case when pos == end separately, to process the final segment without a trailing backslash.

🧹 Nitpick comments (3)
tests/classes/inner_classes/simple_declaration_005.phpt (3)

1-3: Test purpose is clear but could be more descriptive.

The test title "failed inheritance" accurately describes the purpose but could be more specific about the incompatible method signatures being tested.

--TEST--
-failed inheritance
+Failed inheritance with incompatible nested class return types
--FILE--

27-31: Redundant method call detected.

Line 30 duplicates the call to Outer2::testSelf() from line 29. Since the test is designed to fail with a fatal error during class declaration, these method calls won't execute anyway.

var_dump(Outer::testSelf());
var_dump(Outer2::testParent());
var_dump(Outer2::testSelf());
-var_dump(Outer2::testSelf());

1-35: This test correctly validates method signature compatibility with nested classes.

The test demonstrates an important concept: when overriding methods with return types that reference nested classes, the return types must be compatible. This ensures type safety when using inheritance with nested classes.

A few suggestions to enhance the test:

  1. Add a comment explaining why this fails (the return types refer to different classes)
  2. Consider adding a case that would pass (with compatible signatures) for comparison
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24cd8c2 and 1278c20.

📒 Files selected for processing (3)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_opcode.c (1 hunks)
  • tests/classes/inner_classes/simple_declaration_005.phpt (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
Zend/zend_namespaces.c (4)
Zend/zend_namespaces.h (3)
  • name (25-25)
  • name (26-26)
  • zend_destroy_namespaces (27-27)
Zend/zend_compile.h (2)
  • name (988-988)
  • zend_initialize_class_data (983-983)
Zend/zend_execute_API.c (4)
  • zend_class_entry (714-726)
  • zend_class_entry (1174-1295)
  • zend_class_entry (1298-1301)
  • zend_class_entry (1304-1319)
Zend/zend_compile.c (1)
  • zend_class_entry (1406-1446)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (7)
tests/classes/inner_classes/simple_declaration_005.phpt (3)

6-12: LGTM! Correctly defines base class with nested class.

The Outer class and its nested Middle class are properly defined. The testSelf() method correctly returns an instance of the nested class.


14-25: LGTM! Clear demonstration of class inheritance with nested classes.

The Outer2 class properly extends Outer and demonstrates both accessing the parent's nested class and overriding the parent method with its own nested class.


33-34:

❓ Verification inconclusive

Expected output looks correct but check capitalization.

The expected error message format appears to match PHP's style, but ensure the capitalization of "middle" in the error message matches PHP's actual output. PHP error messages typically use the exact case of the class name as defined.


🏁 Script executed:

#!/bin/bash
# Check if PHP's actual error message matches the expected case in the test
php -r "
class Outer {
    public class Middle {
    }
    public static function testSelf(): Middle {
        return new Middle();
    }
}

class Outer2 extends Outer {
    public class Middle {
    }
    public static function testSelf(): Middle {
        return new Middle();
    }
}
" 2>&1 | grep -i "must be compatible"

Length of output: 321


Action: Verify expected error message capitalization in PHP output

The expected output in the test file currently reads:

--EXPECTF--
Fatal error: Declaration of Outer2::testSelf(): Outer2\middle must be compatible with Outer::testSelf(): Outer\middle in %s on line %d

However, PHP typically uses the class name exactly as defined, so if the actual class is declared as Middle, PHP’s error message would likely reference it with an uppercase “M” (i.e., Outer2\Middle). Since our initial shell verification produced no output, please manually verify that PHP outputs the class name with the correct capitalization. If it does, update your expected error message accordingly.

Zend/zend_opcode.c (1)

435-435: Good addition of ZEND_NAMESPACE_CLASS in the destroy_zend_class switch statement

Adding the ZEND_NAMESPACE_CLASS case to the existing switch statement ensures proper cleanup of namespace classes, similar to how internal classes are handled. This is a necessary change to support the newly introduced namespace functionality while maintaining consistent memory management throughout the Zend Engine.

Zend/zend_namespaces.c (3)

90-96: Add NULL check in zend_lookup_namespace function

The function should check if name is NULL to avoid potential crashes. This would also make it consistent with zend_resolve_namespace.

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+      return NULL; // or return EG(global_namespace) for consistency
+   }
    zend_string *lc_name = zend_string_tolower(name);
    zend_class_entry *ns = zend_hash_find_ptr(EG(namespaces), lc_name);
    zend_string_release(lc_name);

    return ns;
}

98-105: Good cleanup in zend_destroy_namespaces

The function properly checks for NULL before destroying the hash table and properly cleans up all allocated resources. It's also setting the pointers to NULL after freeing, which helps prevent use-after-free bugs.


65-88:

✅ Verification successful

Ensure global namespace initialization is thread-safe

The global namespace initialization could have race conditions in a multi-threaded environment. Consider adding synchronization or ensuring this is called within a thread-safe context. Additionally, the function correctly handles NULL or empty namespace names by returning the global namespace.

Let's verify if the execution globals have proper synchronization mechanisms:


🏁 Script executed:

#!/bin/bash
# Look for potential synchronization points in zend_globals.h, zend_execute.h, or related files
rg -A 5 -B 5 "EG\(" --include "*.h" 

Length of output: 479


Thread-Safety Concern in Global Namespace Initialization

  • In Zend/zend_namespaces.c (lines 65–88), the lazy initialization of EG(global_namespace) isn’t guarded by any obvious synchronization (e.g., locks or mutexes). This might lead to race conditions in a multi-threaded environment.
  • Please ensure that this initialization is either protected by proper synchronization mechanisms or is guaranteed to occur in a single-threaded context.
  • The previous search for synchronization points using rg produced an error because of an incorrect flag. Re-running with the correct flag (as in the script above) is advised for manual verification.

🏁 Script executed:

#!/bin/bash
# Re-run search for potential synchronization constructs in header files using the proper glob flag.
rg -A 5 -B 5 "EG\(" -g "*.h"

Length of output: 80012


Thread-Safety Verification for Global Namespace Initialization

After re-running our search and reviewing the relevant header files (notably Zend/zend_globals_macros.h), it appears that the execution globals are accessed via thread‐safe mechanisms (e.g. the ZTS-enabled EG macro uses thread-local storage). This means that, in builds where thread safety is enabled (or in non‑threaded environments), each thread maintains its own instance of these globals, and the lazy initialization in zend_resolve_namespace() is safe.

No explicit locks are present in the function because thread-safety is provided by the underlying engine design. However, please verify that your target build configuration is indeed using ZTS (or is running in a single-threaded context); otherwise, if there’s any chance that multiple threads might share the same executor globals, additional protection would be necessary.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
tests/classes/inner_classes/inheritance.phpt (1)

1-15: 🛠️ Refactor suggestion

The test case should include assertions or error expectations

This test file is testing circular inheritance with nested classes, but the --EXPECT-- section is empty. While this implies the code should run without errors, it doesn't explicitly verify any behavior.

Consider adding either:

  1. Some output statements and corresponding expectations to verify the class hierarchy is correctly established
  2. If you're testing that circular inheritance is properly prevented, include the expected error message

Without explicit verification, it's unclear what specific functionality is being tested and whether the test passes for the right reasons.

-<?php
+<?php
+
 class Outer {
  class Middle extends Outer\Other {
      class Inner1 extends Inner2 {}
      class Inner2 extends Other {}
  }
  abstract class Other {}
 }
+
+// Verify classes are properly defined and accessible
+var_dump(class_exists('Outer'));
+var_dump(class_exists('Outer\\Middle'));
+var_dump(class_exists('Outer\\Middle\\Inner1'));
+var_dump(class_exists('Outer\\Middle\\Inner2'));
+var_dump(class_exists('Outer\\Other'));
 ?>
 --EXPECT--
-
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
♻️ Duplicate comments (3)
Zend/zend_namespaces.c (3)

42-43: ⚠️ Potential issue

Fix off-by-one error in namespace parsing loop

The loop condition while (pos <= end) could lead to reading one byte past the end of the string. Since end points to the null terminator, the condition should be while (pos < end).

-while (pos <= end) {
-   if (pos == end || *pos == '\\') {
+while (pos < end) {
+   if (*pos == '\\') {

45-46: ⚠️ Potential issue

Fix incorrect namespace segment extraction

The string initialization uses ZSTR_VAL(name) (start of the full name) instead of start (beginning of the current segment). This results in creating namespace prefixes instead of individual segments.

-zend_string *needle = zend_string_init(ZSTR_VAL(name), len, 0);
+zend_string *needle = zend_string_init(start, len, 0);

62-64: ⚠️ Potential issue

Update start pointer after segment processing

After processing a namespace segment, the start pointer should be updated to point to the beginning of the next segment.

         parent_ns = ns;
+        start = pos + 1;
      }
      pos ++;
🧹 Nitpick comments (6)
tests/classes/inner_classes/inner_classes.inc (2)

3-9: Class naming follows snake_case instead of PHP's PascalCase convention.

While this code introduces nested classes correctly, the outer class name inner_classes uses snake_case naming convention, which differs from PHP's common practice of using PascalCase for class names (e.g., InnerClasses). Consider updating the naming to follow PascalCase convention for better consistency with PHP standards.


8-8: Empty private class lacks documentation.

The Line class is defined but empty. Consider adding a comment explaining its purpose in this test file (likely to demonstrate access control for inner classes).

-    private class Line {}
+    // Empty class to demonstrate private inner class access control
+    private class Line {}
tests/classes/inner_classes/autoload.phpt (1)

10-11: Verify namespace-like syntax for inner class access.

The test correctly demonstrates that nested classes are accessed using namespace-like syntax (inner_classes\Point), although these are technically inner classes and not namespaced classes. This access pattern should be clearly documented in the RFC to avoid confusion for developers.

This access pattern represents a significant design decision that merges aspects of namespaces and class hierarchies. Consider ensuring this is thoroughly documented in the RFC and PHP documentation to help developers understand the conceptual model.

tests/classes/inner_classes/readonly.phpt (1)

1-3: Consider making the test title more specific

The current title "readonly should work" could be more descriptive about what aspect of readonly functionality is being tested.

---TEST--
-readonly should work
+readonly nested class should work
tests/classes/inner_classes/reflection_001.phpt (1)

16-17: Variable defined but unused

The $outer variable is defined but never used in the code. Consider removing this unused variable or utilizing it in the test.

-$outer = new \ReflectionClass(Outer::class);
tests/classes/inner_classes/inheritance.phpt (1)

7-10: Consider adding explicit tests for the circular inheritance detection

The test name is "circular inheritance," but the class structure doesn't appear to create an actual circular dependency (where A extends B extends A). The current inheritance chain is:

  • Middle extends Outer\Other
  • Inner1 extends Inner2
  • Inner2 extends Other (presumably Outer\Other)

To properly test circular inheritance detection, you should include a case where a class attempts to extend itself either directly or through an inheritance chain.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da979a5 and 5eec334.

📒 Files selected for processing (30)
  • Zend/tests/errmsg/errmsg_027.phpt (1 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_opcode.c (2 hunks)
  • ext/reflection/php_reflection.c (1 hunks)
  • ext/reflection/php_reflection.stub.php (1 hunks)
  • ext/reflection/php_reflection_arginfo.h (4 hunks)
  • ext/reflection/tests/bug74454.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_001.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_002.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_003.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_004.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_005.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_006.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_007.phpt (1 hunks)
  • tests/classes/inner_classes/autoload.phpt (1 hunks)
  • tests/classes/inner_classes/enum_usage.phpt (1 hunks)
  • tests/classes/inner_classes/errors_001.phpt (1 hunks)
  • tests/classes/inner_classes/errors_002.phpt (1 hunks)
  • tests/classes/inner_classes/inheritance.phpt (1 hunks)
  • tests/classes/inner_classes/inner_classes.inc (1 hunks)
  • tests/classes/inner_classes/interface_usage.phpt (1 hunks)
  • tests/classes/inner_classes/readonly.phpt (1 hunks)
  • tests/classes/inner_classes/reflection_001.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_001.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_002.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_003.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_004.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_005.phpt (1 hunks)
  • tests/classes/inner_classes/static_variables.phpt (1 hunks)
  • tests/classes/inner_classes/trait_usage_001.phpt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • ext/reflection/tests/bug74454.phpt
  • Zend/tests/errmsg/errmsg_027.phpt
  • tests/classes/inner_classes/simple_declaration_001.phpt
  • tests/classes/inner_classes/simple_declaration_002.phpt
  • tests/classes/inner_classes/simple_declaration_005.phpt
  • Zend/zend_opcode.c
  • tests/classes/inner_classes/simple_declaration_003.phpt
  • tests/classes/inner_classes/simple_declaration_004.phpt
🧰 Additional context used
🧬 Code Definitions (1)
Zend/zend_namespaces.c (3)
Zend/zend_namespaces.h (3)
  • name (25-25)
  • name (26-26)
  • zend_destroy_namespaces (27-27)
Zend/zend_compile.h (2)
  • name (988-988)
  • zend_initialize_class_data (983-983)
Zend/zend_execute_API.c (4)
  • zend_class_entry (714-726)
  • zend_class_entry (1174-1295)
  • zend_class_entry (1298-1301)
  • zend_class_entry (1304-1319)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (41)
tests/classes/inner_classes/errors_001.phpt (1)

1-12: Test validates inner class instantiation without outer class definition

The test correctly verifies that an attempt to instantiate an inner class (Outer\Inner) without first defining its outer class (Outer) results in a fatal error. This is consistent with expected behavior for nested classes.

tests/classes/inner_classes/errors_002.phpt (1)

1-15: Test validates non-existent inner class instantiation

This test appropriately checks that attempting to instantiate a non-existent inner class (Outer\Inner) results in a fatal error, even when the outer class (Outer) exists. The expected error message confirms the class was not found.

tests/classes/inner_classes/inner_classes.inc (1)

4-6: LGTM! Public inner class with constructor property promotion.

The Point inner class correctly implements PHP 8.0+ constructor property promotion for its coordinates, making the code concise and readable.

tests/classes/inner_classes/autoload.phpt (2)

5-8: LGTM! Autoloader implementation correctly handles class loading.

The autoloader function is properly registered and extracts the first part of the namespace to load the appropriate include file. The debug output helps verify the autoloading process.


13-15: LGTM! Expected output matches the test logic.

The expected output correctly reflects the autoloader notification and property values that should be generated by the test case.

Zend/zend_namespaces.c (4)

24-32: The function looks correctly implemented.

The create_namespace function properly allocates memory for a zend_class_entry and initializes it with the correct type and flags. The string copy operation ensures proper memory management.


71-94: The function is correctly implemented.

The zend_resolve_namespace function properly initializes the global namespace if needed and handles the case where the name is NULL or empty. It correctly converts the name to lowercase before lookup and creates the namespace if it doesn't exist.


96-102: Add NULL check in zend_lookup_namespace

The function should check if name is NULL to avoid potential crashes. This would also make it consistent with zend_resolve_namespace.

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+      return NULL; // or return EG(global_namespace) for consistency
+   }
    zend_string *lc_name = zend_string_tolower(name);
    zend_class_entry *ns = zend_hash_find_ptr(EG(namespaces), lc_name);
    zend_string_release(lc_name);

104-111: The cleanup function is properly implemented.

The zend_destroy_namespaces function correctly checks if the namespaces hash table exists before destroying it and freeing its memory. It also properly resets the global namespace pointer.

tests/classes/inner_classes/access_modifiers_003.phpt (1)

1-14: Test correctly validates that static modifier is disallowed on nested classes

This test case is well-structured and properly verifies that the static keyword cannot be used as a modifier for nested classes. The test follows PHP's testing conventions with clear expectations and appropriate format specifiers for the error message.

tests/classes/inner_classes/access_modifiers_001.phpt (1)

1-14: Test correctly validates that multiple access modifiers are disallowed

This test case properly verifies that using multiple access modifiers (public private) on a nested class results in a fatal error. The test follows PHP's testing conventions and correctly validates that nested classes follow the same rules as regular classes regarding access modifiers.

tests/classes/inner_classes/access_modifiers_005.phpt (1)

1-14: Test correctly validates that protected(set) modifier is disallowed on nested classes

This test case properly verifies that the protected(set) modifier cannot be applied to nested classes. The test is well-structured with appropriate error message validation and follows PHP's testing conventions.

tests/classes/inner_classes/enum_usage.phpt (1)

1-17: Test correctly validates nested class usage within an enum

This test case properly demonstrates that nested classes can be defined within an enum and accessed using the namespace notation (Outer\Inner). The test verifies both instantiation and class existence, confirming that the nested class behaves as expected.

tests/classes/inner_classes/access_modifiers_006.phpt (1)

1-14: Well-structured test for private(set) modifier restriction on nested classes

This test correctly validates that the private(set) modifier cannot be applied to a nested class, which is a sensible constraint since this modifier is designed for properties, not classes.

The test format follows PHP's standard .phpt structure with clear sections for the test description, code, and expected output. The error message is appropriately defined with placeholders for dynamic content.

tests/classes/inner_classes/access_modifiers_004.phpt (1)

1-14: Well-structured test for public(set) modifier restriction on nested classes

This test correctly validates that the public(set) modifier cannot be applied to a nested class, similar to the previous test with private(set). This is a logical constraint as property modifiers shouldn't be applicable to class declarations.

The test follows the standard .phpt format and provides a clear error message for developers encountering this incorrect usage.

tests/classes/inner_classes/access_modifiers_002.phpt (1)

1-14: Valid test for syntax error detection in nested class declarations

This test correctly validates that the parser can detect invalid syntax in nested class declarations, specifically when an unexpected token (int) appears before the class keyword.

The error message is clear and helpful, indicating what was expected (class) and what was encountered instead (int), which provides good guidance for developers.

tests/classes/inner_classes/readonly.phpt (3)

6-8: Well-implemented readonly nested class with constructor property promotion

This test uses modern PHP features effectively - a readonly nested class with constructor property promotion for initializing the $t property. The code is concise and demonstrates both the nesting relationship and readonly functionality.


10-13: Good test coverage for readonly property enforcement

This section properly tests the readonly behavior by:

  1. Creating an instance with an initial value
  2. Attempting to modify the readonly property
  3. Verifying the property remains unchanged

This approach effectively validates both successful initialization and proper enforcement of the readonly constraint.


14-24: Complete and accurate expected output section

The expected output correctly shows:

  1. The initial object state with the property value
  2. The fatal error when attempting to modify a readonly property
  3. The stack trace with appropriate placeholders for dynamic values

This provides thorough verification of the readonly behavior.

tests/classes/inner_classes/trait_usage_001.phpt (2)

4-18: Well-structured test case for inner class behavior within traits.

The test correctly verifies that inner classes within traits are accessible via the trait's namespace but aren't transferred to classes that use the trait. This confirms the intended behavioral boundary between trait usage and inner class scoping.


20-24: Expected output aligns with proper behavior for inner classes.

The test expectations correctly assert that:

  1. Outer\Inner can be instantiated as an object
  2. Outer\Inner exists as a class
  3. Foo\Inner does not exist, confirming that inner classes don't get "copied" when traits are used

This maintains proper encapsulation semantics for nested classes.

tests/classes/inner_classes/access_modifiers_007.phpt (3)

6-8: Correctly tests abstract modifier on inner classes.

The test properly defines an abstract inner class with a public modifier, verifying that access modifiers and class type modifiers can be combined for inner classes.


10-17: Comprehensive test for abstract inner class behavior.

The test covers essential scenarios for abstract inner classes:

  1. Extending an abstract inner class
  2. Instantiating the extended class
  3. Using reflection to verify the abstract property
  4. Testing that direct instantiation fails with appropriate error

This verifies both inheritance and instantiation rules are properly enforced.


19-27: Expected output properly validates abstract class behavior.

The test expectations correctly capture:

  1. Successful instantiation of the extending class
  2. Confirmation that the inner class is abstract
  3. The proper fatal error when attempting to instantiate the abstract class directly

This ensures proper language semantics for abstract classes are maintained in the nested class context.

tests/classes/inner_classes/interface_usage.phpt (2)

4-16: Well-designed test for inner classes within interfaces.

The test correctly verifies that:

  1. Interfaces can contain inner classes
  2. Inner classes within interfaces can be instantiated directly
  3. Classes implementing an interface don't inherit the inner classes

This is consistent with the behavior seen in traits and maintains proper encapsulation semantics.


17-21: Expected output correctly validates interface inner class behavior.

The expected output properly confirms that:

  1. Outer\Inner can be instantiated successfully
  2. Outer\Inner class exists and is accessible
  3. Foo\Inner doesn't exist, confirming inner classes aren't transferred to implementing classes

This maintains consistent behavior with the trait inner class tests.

ext/reflection/php_reflection.stub.php (1)

436-442: Appropriate extension of the ReflectionClass API for nested classes.

The four new methods enhance PHP's reflection capabilities to support nested classes:

  1. isInnerClass(): Detects if a class is nested within another class
  2. isPrivate(), isPublic(), isProtected(): Check access modifiers for classes

These methods follow the established naming conventions of the Reflection API and complete the set of visibility inspection methods for classes that were previously missing.

tests/classes/inner_classes/reflection_001.phpt (4)

8-14: LGTM: Comprehensive class structure for testing nested class reflection

The class hierarchy correctly demonstrates nested classes with various visibility modifiers (public, private, protected), which is essential for testing the reflection capabilities.


19-28: LGTM: Well-structured test helper function

The details() function effectively tests all the relevant reflection methods for nested classes, including name resolution and visibility checks.


30-34: LGTM: Good test coverage for different class types

The test invocations cover all important nested class scenarios: regular nested classes, deeply nested classes, and classes with different visibility modifiers.


36-64: LGTM: Expected output matches inner class behavior

The expected output correctly validates the reflection behavior for nested classes, confirming:

  1. Proper name resolution (both full and short names)
  2. Correct identification of inner classes
  3. Appropriate visibility reporting for each class type
tests/classes/inner_classes/static_variables.phpt (3)

6-15: LGTM: Well-structured nested class definition with static members

The class hierarchy correctly demonstrates nested classes with static members (constants and variables), providing a good test case for accessing these members through nested class paths.


17-22: LGTM: Comprehensive testing of static member access

The test covers all essential scenarios for accessing static members in nested classes:

  1. Class name resolution through ::class
  2. Constant access with ::FOO
  3. Static variable access with ::$bar

Each test is performed at different nesting levels, providing thorough coverage.


24-30: LGTM: Expected output correctly validates nested class access

The expected output accurately reflects the behavior of static member access in nested classes, confirming:

  1. Proper class name resolution with appropriate namespacing
  2. Successful access to constants across nesting levels
  3. Correct handling of static variables in the nested class structure
ext/reflection/php_reflection.c (4)

4079-4088: Well-implemented method to check if a class is private.

The implementation correctly checks for both required_scope and required_scope_absolute properties to determine if a class is private. This aligns with PHP's implementation of nested classes where these flags indicate private visibility.


4091-4099: Correct implementation for protected class check.

The method properly verifies that a class is protected by checking that required_scope is set but required_scope_absolute is false, which correctly identifies protected visibility for nested classes.


4102-4111: Appropriate implementation for public class check.

The method correctly determines if a class is public by checking that required_scope is not set, which is the expected behavior for public class visibility within the nested class implementation.


4114-4125: Well-implemented method to identify inner classes.

This method properly checks if a class is an inner class by verifying that lexical_scope is set and its type is not ZEND_NAMESPACE_CLASS. This correctly distinguishes between nested classes and classes that are simply in a namespace.

ext/reflection/php_reflection_arginfo.h (3)

369-375: New ReflectionClass methods for nested class support

These new method declarations add reflection capabilities for nested classes in PHP, adding the ability to check if a class is an inner class and to determine its visibility modifiers (public, private, protected).


858-861: Implementation of nested class reflection methods

The implementation of the new reflection methods matches the standard pattern used throughout the file. These methods will be crucial for inspecting nested classes in the PHP reflection API.


1154-1157: Added entries to function table

The new methods are properly registered in the ReflectionClass function table, making them accessible through the PHP reflection API.

class Outer {
class Middle extends Outer\Other {
class Inner1 extends Inner2 {}
class Inner2 extends Other {}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Verify class resolution behavior in Inner2

The Inner2 class extends Other without any namespace qualification. In nested classes, it's important to confirm whether this resolves to Outer\Other (as likely intended) or if it looks for an Other class in the global namespace.

This test would be more thorough if it explicitly verified the parent class of Inner2.


🏁 Script executed:

#!/bin/bash
# Check if similar test cases exist that verify class resolution
grep -r "get_parent_class" tests/classes/inner_classes/

Length of output: 55


Action Required: Explicitly Verify Parent Class Resolution for Inner2

The current test in tests/classes/inner_classes/inheritance.phpt does not include any assertions (e.g., via get_parent_class) to verify that the unqualified class name in Inner2 extends Other correctly resolves to Outer\Other. Since our grep check for get_parent_class in this test directory produced no output, it appears that there is no explicit verification of the parent's resolution behavior.

  • Add an assertion to confirm that get_parent_class(Inner2) returns Outer\Other (or the expected fully qualified class name)
  • Consider updating the test file to include checks for class resolution, ensuring that nested classes rely on the appropriate namespace context

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (12)
tests/classes/inner_classes/visibility_006.phpt (1)

1-28: LGTM! Well-structured test for nested class scope isolation.

This test correctly verifies that inner classes can't access private methods from outer classes. The hierarchy (Outer->Middle->Inner) with same-named methods at different visibility levels provides good coverage of scope isolation.

Consider adding a more descriptive comment at the top of the file explaining that this test specifically verifies that an inner class cannot access private methods from its outer classes, even though they share the class hierarchy.

tests/classes/inner_classes/visibility_001.phpt (1)

1-26: LGTM! Good test for visibility enforcement.

This test correctly verifies that a private inner class cannot be exposed through a public property of its containing class.

There appears to be a typo in the expected error message: "higher visibile property" should probably be "higher visible property". Consider fixing this if it matches the actual PHP engine output, or updating the engine's error message.

The test name "outer class visibility" could be more specific, perhaps "private inner class cannot be exposed through public property".

tests/classes/inner_classes/visibility_004.phpt (1)

1-26: LGTM! Good test for protected class visibility enforcement.

This test correctly verifies that a protected inner class cannot be exposed through a public property of its containing class.

There appears to be a typo in the expected error message: "higher visibile property" should probably be "higher visible property". Consider fixing this if it matches the actual PHP engine output, or updating the engine's error message.

Consider using a more specific test name than "outer class visibility" to clarify the specific aspect being tested (i.e., "protected inner class cannot be exposed through public property").

This test is very similar to visibility_001.phpt but for protected classes instead of private classes. Consider adding a comment to clarify the distinction or potentially combining them into a parameterized test.

tests/classes/inner_classes/visibility_009.phpt (1)

6-25: LGTM! Comprehensive test for private property access in nested classes.

This class hierarchy with its test method effectively demonstrates that inner classes have privileged access to private members of their outer classes, both static and instance properties.

Consider adding a comment to explicitly state the rule being tested: inner classes have access to private members of their outer classes. This would make the purpose of the test clearer to someone unfamiliar with nested class visibility rules.

tests/classes/inner_classes/reflection_001.phpt (3)

16-16: Unused variable $outer.

The $outer variable is created but never used in the rest of the code. Consider removing this line or using the variable for additional tests.

-$outer = new \ReflectionClass(Outer::class);

17-18: Unnecessary empty lines.

There are two consecutive empty lines here. Consider removing one for better code organization.

$outer = new \ReflectionClass(Outer::class);

-
function details($ref) {

30-34: Consider adding a direct test for the Middle class.

While you're testing the Inner, PrivateMiddle, and ProtectedMiddle classes, there's no direct test for the Middle class itself. Consider adding a test case for it to ensure complete coverage.

details(Outer::class);
+details('n\s\Outer\Middle');
details('n\s\Outer\Middle\Inner');
details('n\s\Outer\PrivateMiddle');
details('n\s\Outer\ProtectedMiddle');
Zend/zend_object_handlers.c (5)

384-384: Consider factoring out repeated label usage.
Introducing the check_lexical_scope: label suggests a goto-based approach for scope checks. While not incorrect, ensure consistent usage throughout the file to avoid future confusion.


1845-1848: Avoid duplicating lexical scope fallback logic.
This block repeats the same pattern of descending to the lexical scope. Consider extracting into a helper function to reduce code duplication.


1908-1910: Standardize the “original scope” tracking approach.
Lines 1908–1910 replicate the same pattern used elsewhere. To streamline maintainability, unify this pattern in a shared function or macro if feasible.


1998-1998: Label usage flagged for potential re-check.
The check_lexical_scope: label reappears here. Ensure consistent behavior if macros or shared functions exist.


2003-2006: Consolidate repeated “lexical scope” checks.
These lines mirror earlier blocks. A single function or macro for unwrapping nested lexical scopes might reduce repetition and lower the risk of future regressions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eec334 and 9f5f2a4.

📒 Files selected for processing (16)
  • Zend/zend_object_handlers.c (7 hunks)
  • ext/opcache/zend_persist.c (2 hunks)
  • ext/reflection/php_reflection.c (1 hunks)
  • ext/reflection/php_reflection.stub.php (1 hunks)
  • ext/reflection/php_reflection_arginfo.h (4 hunks)
  • tests/classes/inner_classes/reflection_001.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_001.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_002.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_003.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_004.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_005.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_006.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_007.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_008.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_009.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_010.phpt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ext/reflection/php_reflection.stub.php
🧰 Additional context used
🧬 Code Definitions (1)
ext/reflection/php_reflection_arginfo.h (1)
ext/reflection/php_reflection.stub.php (14)
  • ReflectionClass (233-443)
  • isInnerClass (436-436)
  • isPrivate (191-191)
  • isPrivate (438-438)
  • isPrivate (516-516)
  • isPrivate (612-612)
  • isPublic (188-188)
  • isPublic (440-440)
  • isPublic (513-513)
  • isPublic (609-609)
  • isProtected (194-194)
  • isProtected (442-442)
  • isProtected (519-519)
  • isProtected (615-615)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: BENCHMARKING
🔇 Additional comments (33)
ext/opcache/zend_persist.c (2)

1124-1143: Properly updates class scope for persistence in shared memory

The new zend_update_ce_scopes function correctly handles the translation of required_scope and lexical_scope pointers when persisting class entries to shared memory. This is a critical component for nested classes support.

The implementation follows the established pattern in this codebase for updating pointers when moving data to shared memory - first checking if the pointer exists, then finding its translated location using zend_shared_alloc_get_xlat_entry, and finally updating the pointer if a translation is found.


1318-1318: Good integration with existing persist workflow

The call to zend_update_ce_scopes(ce) is appropriately placed after zend_update_parent_ce(ce) in the class persistence loop, ensuring that all class relationship pointers are properly translated when moving class entries to shared memory.

tests/classes/inner_classes/visibility_002.phpt (1)

1-31: Well-structured test case for inner class access to outer class private members

This test effectively demonstrates that an inner class can access private variables of its outer class, which is a key feature of nested classes. The inner class is treated as part of the outer class's scope, allowing it to modify private members that would be inaccessible to external code.

The test clearly shows:

  1. A private inner class definition
  2. Accessing and modifying a private outer class property
  3. Verification through var_dump that the property was correctly set

The expected output matches what would happen if the nested class implementation follows OOP encapsulation principles correctly.

tests/classes/inner_classes/visibility_008.phpt (1)

1-31: Good test case for sibling method accessibility in inheritance hierarchies

This test effectively validates that inner classes in a derived class can access protected members of the base class and its inner classes. This is important to ensure proper inheritance behavior with nested classes.

The test structure demonstrates:

  1. A class hierarchy with protected methods and nested classes
  2. Access to a protected static method from a sibling class
  3. Access to a protected instance method from the base class
  4. Verification through output that both methods are accessible

This confirms that visibility modifiers work correctly across the inheritance hierarchy when using nested classes.

tests/classes/inner_classes/visibility_005.phpt (1)

1-28: Well-designed test for deeply nested class access to private methods

This test effectively demonstrates that inner classes can access private methods of their outer classes at any nesting level. This is crucial functionality for nested classes.

The test shows:

  1. Multiple levels of class nesting (Outer → Middle → Inner)
  2. Access to a private static method from the immediate parent class
  3. Access to a private instance method from the grandparent class
  4. Verification that both methods can be called successfully

This confirms that nested classes preserve proper scope access even with deep nesting, which is essential for maintaining the expected OOP encapsulation model.

tests/classes/inner_classes/visibility_007.phpt (1)

1-28: Comprehensive test for protected method access in nested class hierarchies

This test properly validates that inner classes can access protected methods of their outer classes regardless of nesting level. This complements the other visibility tests by focusing on the protected access modifier.

Key aspects tested:

  1. Access to a protected static method from the immediate parent class
  2. Access to a protected instance method from the grandparent class
  3. Verification of correct method invocation through output

The test structure mirrors visibility_005.phpt (which tests private methods), providing good coverage of both access modifiers. This consistency in test design helps ensure comprehensive validation of the nested class visibility implementation.

tests/classes/inner_classes/visibility_003.phpt (5)

1-3: Well-structured test description.

The test clearly describes its purpose of testing access to outer protected variables in a nested class context.


6-12: Good demonstration of nested class access patterns.

The code effectively demonstrates how an inner class can access and modify private properties of its outer class. The nested class structure is properly defined with appropriate visibility modifiers.


14-17: Appropriate encapsulation of inner class instantiation.

The outer class correctly instantiates its inner class and passes $this as a reference, maintaining proper encapsulation while allowing the inner class to act on the outer instance.


19-22: Test execution flow is clear and concise.

The test case follows a logical flow: creating an instance, invoking the method, and then verifying the result with var_dump.


26-30: Expected output validates correct namespace handling.

The expected output correctly shows the nested class's fully qualified name as Outer\Inner, confirming proper namespace handling for nested classes.

tests/classes/inner_classes/visibility_010.phpt (7)

6-9: Excellent use of private(set) visibility modifier.

The public private(set) property visibility allows read access from outside the class while restricting write access to within the class, which is ideal for immutable objects.


10-13: Private constructor enforces builder pattern usage.

Making the constructor private ensures that users must use the Builder class to create instances, enforcing the intended API design.


15-17: Well-designed nested builder class with appropriate modifiers.

The nested Builder class makes excellent use of:

  1. readonly final to ensure immutability and prevent extension
  2. Constructor property promotion for concise property initialization
  3. Nullable types (string|null) for optional parameters

This showcases advanced PHP features while maintaining clean code.


18-24: Immutable builder pattern implemented correctly.

The with* methods return new instances rather than modifying the current one, correctly implementing the immutable builder pattern. This approach prevents side effects and makes the code more predictable.


26-29: Build method completes the builder pattern implementation.

The build method correctly finalizes the builder pattern by creating a new User instance with the configured properties.


32-33: Fluent interface demonstrates elegant API usage.

The test demonstrates how nested classes can create elegant, fluent APIs. The method chaining pattern makes the code more readable and expressive.


36-41: Expected output validates object creation.

The expected output correctly verifies that the User object was created with the appropriate property values, confirming the builder pattern works as intended.

tests/classes/inner_classes/reflection_001.phpt (3)

8-14: Good test case for nested classes with various visibility modifiers.

The class structure effectively tests nested classes with different visibility modifiers (public, private, protected). This is important for validating that the reflection API correctly identifies these modifiers for nested classes.


19-28: Well-designed test function for reflection properties.

The details() function appropriately checks several reflection properties including class name, short name, and various visibility status methods. This provides good coverage for testing reflection on nested classes.


37-64: Consistent expected output for reflection tests.

The expected output correctly reflects the class hierarchy and visibility modifiers of the nested classes. This ensures that the reflection API correctly identifies inner classes and their properties.

ext/reflection/php_reflection.c (4)

4079-4088: Looks good - adds method to check if a class is private

The implementation correctly determines if a class is private by checking both the required_scope and required_scope_absolute properties, returning true only when both are set.


4091-4100: Looks good - adds method to check if a class is protected

The implementation properly determines if a class is protected by checking that required_scope is set but required_scope_absolute is false, which is the correct logic for protected class visibility.


4103-4112: Looks good - adds method to check if a class is public

The implementation correctly identifies a public class by checking that required_scope is not set, which aligns with the visibility model being implemented.


4115-4124: Looks good - adds method to check if a class is an inner class

This method properly determines if a class is an inner class by verifying that it has a lexical scope that is not a namespace class. The implementation is consistent with the RFC for nested classes.

Zend/zend_object_handlers.c (5)

406-409: Prevent potential infinite recursion when changing scope.
Repeatedly setting scope = scope->lexical_scope; could lead to cycles if scope->lexical_scope references a higher-level scope. Consider adding safeguards (e.g., tracking visited scopes) or a loop boundary check to avoid an infinite loop.


1826-1827: Track original scope before re-checking lexical scopes.
Storing original_scope is good for error reporting, but be cautious if further modifications to scope need to restore this original context.


1916-1919: Add a protection mechanism for lexical scope re-assignment.
Repeatedly assigning scope->lexical_scope might trigger obscure bugs if nested classes share scope references in unexpected ways. Perform cyc checks or limit layering depth to prevent accidental loops.


2087-2088: Preserve original scope in constructor calls.
Similar to other scope-introspection blocks, storing original_scope helps maintain correct error messages if a lexical-scope fallback is triggered.


2092-2095: Check for potential scope cycles in constructors.
Shifting scope to scope->lexical_scope repeatedly can lead to logical loops if nesting is cyclical. Verify that such a cycle cannot arise or add a check that breaks the loop if necessary.

ext/reflection/php_reflection_arginfo.h (3)

369-375: New reflection methods for class visibility and nesting status.

These new argument definitions appropriately use the existing tentative return type pattern from hasTentativeReturnType, ensuring consistent boolean return values for the new reflection methods.


858-861: Method declarations for inner class and visibility reflection support.

The new methods isInnerClass, isPrivate, isPublic, and isProtected properly declare the reflection capabilities needed for the nested classes feature.


1154-1157: Method registration in reflection class implementation.

Methods are correctly registered in the ReflectionClass_methods array, making them publicly accessible. These additions follow the established pattern for reflection method registration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5f2a4 and 8bb387e.

📒 Files selected for processing (3)
  • Zend/zend_execute.c (1 hunks)
  • tests/classes/inner_classes/visibility_001.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_004.phpt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/classes/inner_classes/visibility_001.phpt
  • tests/classes/inner_classes/visibility_004.phpt
🧰 Additional context used
🧬 Code Definitions (1)
Zend/zend_execute.c (3)
Zend/zend_object_handlers.c (3)
  • bool (259-270)
  • bool (971-976)
  • bool (1591-1615)
Zend/zend_compile.c (4)
  • bool (309-323)
  • bool (528-531)
  • bool (2070-2080)
  • bool (2083-2094)
Zend/zend_execute.h (1)
  • zend_verify_property_type_error (528-528)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: BENCHMARKING
🔇 Additional comments (1)
Zend/zend_execute.c (1)

1048-1079: Verify the subclass check direction in instanceof_function().

The comment states that a "protected class is visible if it is a subclass of the lexical scope," yet the code calls instanceof_function(info->ce, ce->required_scope). This indicates that info->ce must derive from ce->required_scope. Please confirm that this is the intended relationship (i.e., that info->ce should be the child class). If the logic is reversed, you may need to swap the arguments or revise the comment accordingly.

Comment on lines +1082 to +1091
if (Z_TYPE_P(property) == IS_OBJECT && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) {
zend_verify_property_type_error(info, property);
return 0;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential mismatch of return codes in zend_check_type_visibility.

Because zend_check_type_visibility() returns SUCCESS (0) on success and FAILURE (1) on error, the condition:

if (zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) {
    ...
}

will be true if the function returns failure, not success. For clarity and correctness, consider making the check explicit, for example:

- if (Z_TYPE_P(property) == IS_OBJECT && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) {
+ if (Z_TYPE_P(property) == IS_OBJECT
+     && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags) == FAILURE) {

This prevents confusion and ensures the code triggers on an actual failure rather than success.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/classes/inner_classes/return_types_002.phpt (1)

18-23: Consider consistent return type notation

While the code works correctly, consider using consistent return type notation between parent and child classes. The parent uses Inner while the child uses Outer\Inner. Both are valid, but consistency would improve readability.

-    public function getInner(): Outer\Inner {
+    public function getInner(): \Outer\Inner {

This change adds a leading backslash to make it clear it's a fully qualified class name.

tests/classes/inner_classes/return_types_005.phpt (1)

1-27: Test verifies protected class return type constraints with static methods

This test correctly validates that a public static method cannot expose a protected inner class to external code, throwing a TypeError. This maintains proper encapsulation of protected members.

However, line 18-19 may never execute because the error occurs earlier at line 14 when Outer::getInner() is called, making the var_dump and test function call unreachable.

Consider moving line 14 $r = Outer::getInner(); after the function declaration, closer to where it's used, to make the execution flow clearer:

-$r = Outer::getInner();
function test($r): Outer\Inner {
    return $r;
}
+$r = Outer::getInner();
var_dump($r);
test($r);
tests/classes/inner_classes/return_types_004.phpt (1)

1-27: Test correctly validates private class return type visibility rules

This test properly verifies that a public method cannot return a private inner class type, enforcing PHP's strict visibility rules for nested classes.

As with the previous test, lines 18-19 are unreachable since the error occurs earlier at line 14. The execution flow would be clearer if the variable assignment was placed closer to where it's used.

Consider moving line 14 $r = Outer::getInner(); after the function declaration, closer to where it's used, to make the execution flow clearer:

-$r = Outer::getInner();
function test($r): Outer\Inner {
    return $r;
}
+$r = Outer::getInner();
var_dump($r);
test($r);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bb387e and 4fc91b2.

📒 Files selected for processing (10)
  • Zend/Optimizer/dfa_pass.c (1 hunks)
  • ext/reflection/tests/ReflectionClass_toString_001.phpt (2 hunks)
  • tests/classes/inner_classes/autoload_001.phpt (1 hunks)
  • tests/classes/inner_classes/autoload_002.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_001.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_002.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_003.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_004.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_005.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_006.phpt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (13)
Zend/Optimizer/dfa_pass.c (1)

285-285: Additional check for nested classes in type elision logic.

The added condition !ce->required_scope ensures that type elision doesn't bypass necessary type checks for nested classes where a scope requirement exists. This is a critical safety measure for the new nested classes feature.

ext/reflection/tests/ReflectionClass_toString_001.phpt (3)

33-33: Method count updated correctly.

The method count has been updated from 64 to 68 to accurately reflect the addition of the four new visibility and scope inspection methods.


518-523: New method for inner class detection looks good.

The isInnerClass method is a well-implemented addition that will allow developers to determine if a class is an inner class, which is essential for the new nested class functionality.


525-530: Class visibility inspection methods properly implemented.

The three new visibility methods (isPrivate, isPublic, isProtected) follow the established pattern for accessor methods in ReflectionClass. They're correctly defined with no parameters and a boolean return type, providing a complete set of visibility checks that will be essential for working with nested classes.

Also applies to: 532-537, 539-544

tests/classes/inner_classes/autoload_002.phpt (1)

1-20: Test correctly verifies private class autoloading behavior

This test properly validates that while private classes can be autoloaded, they cannot be instantiated from outside their scope. The test demonstrates that the autoload mechanism is triggered successfully, but the instantiation fails with an appropriate error message.

tests/classes/inner_classes/return_types_001.phpt (2)

6-12: Return type with fully qualified inner class name looks good

The test correctly demonstrates that a static method within an inner class can return an instance of itself using the fully qualified name as the return type.


14-18: Test successfully validates inner class return type

The test correctly verifies that the returned object matches the expected type Outer\Inner.

tests/classes/inner_classes/autoload_001.phpt (2)

5-8: Autoload function implementation is appropriate for testing

The autoload function correctly constructs the file path using the first segment of the namespace and includes the file. The notification of autoloading via echo is helpful for debugging and test verification.


10-15: Test successfully verifies public class autoloading

This test correctly demonstrates that a public class in a namespace can be autoloaded and instantiated from the global scope.

tests/classes/inner_classes/return_types_002.phpt (2)

6-16: Private inner class implementation is correct

The Outer class correctly defines a private inner class and private/public methods that work with it. The return type in getInner() uses the short form Inner which is valid within the context of Outer.


25-36: Test correctly verifies private class visibility rules

This test effectively demonstrates that a child class can access an instance of a parent's private inner class when returned through a public method (getInner2()), but cannot directly instantiate that private inner class. The error message correctly indicates this visibility constraint.

tests/classes/inner_classes/return_types_006.phpt (1)

1-21: Test correctly validates returning a private inner class instance from an inner method

This test case properly validates that an instance of a private inner class can be returned from the inner class's own method and then accessed through a public method of the outer class. This showcases proper encapsulation principles where private members can be exposed through controlled public interfaces.

The test demonstrates that:

  1. PrivateInner is accessible from within Outer
  2. PrivateInner can create instances of itself
  3. The outer class can expose these instances through its public API
tests/classes/inner_classes/return_types_003.phpt (1)

1-34: Test correctly verifies protected inner class visibility boundaries

The test properly validates the visibility rules for protected inner classes:

  1. A protected inner class is accessible within its outer class
  2. A subclass can access the protected inner class of its parent
  3. A public method cannot return a protected class type directly to external code

The expectation correctly shows a TypeError when trying to return the protected Outer\Inner from a public method, enforcing PHP's visibility rules for nested classes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
Zend/zend_vm_execute.h (1)

10802-10819: Consider factoring out repeated scoping checks.

These lines introduce logic to verify that public and protected methods do not return private or protected classes. However, the same pattern of checks and error messages appears multiple times across the file. Extracting a dedicated helper function or macro to handle these scoping checks can reduce duplication, improve maintainability, and keep the code DRY.

Zend/zend_API.c (1)

1838-1839: Early exit might be worth a protective branch.
The code block at line 1839 is left empty and looks more like a brace closure rather than additional logic. If you intended to handle further conditions upon exiting the if, consider adding a comment or a return to clarify the code path.

Add a clarifying comment or return statement if no additional logic is planned:

 }
+// End of required_scope checks
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fc91b2 and 64f1bb5.

📒 Files selected for processing (6)
  • Zend/zend_API.c (1 hunks)
  • Zend/zend_vm_def.h (1 hunks)
  • Zend/zend_vm_execute.h (5 hunks)
  • tests/classes/inner_classes/autoload_002.phpt (1 hunks)
  • tests/classes/inner_classes/instantiation_001.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_002.phpt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/classes/inner_classes/autoload_002.phpt
  • tests/classes/inner_classes/return_types_002.phpt
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: BENCHMARKING
  • GitHub Check: FREEBSD
🔇 Additional comments (13)
tests/classes/inner_classes/instantiation_001.phpt (5)

1-13: Well-structured test for inner class instantiation.

This test file effectively sets up an Outer class with three inner classes having different visibility modifiers (private, protected, and public). The structure is clean and provides a good foundation for testing visibility rules.


15-31: Thorough testing of global scope instantiation.

The code correctly tests instantiation of all three inner classes from the global scope using appropriate try-catch blocks. This verifies that visibility rules prevent instantiation of private and protected inner classes from the global scope, while allowing public inner classes to be instantiated.


33-52: Clean implementation of unrelated class instantiation tests.

The test properly verifies that an unrelated class (Other) cannot instantiate private or protected inner classes. The loop-based approach with dynamic method calls is an elegant way to test multiple scenarios with concise code.


54-73: Good coverage of child class inheritance rules.

This section correctly tests that a child class can access protected inner classes from its parent, but not private inner classes. The test structure mirrors the previous section, maintaining consistency throughout the test file.


76-89: Expected output accurately reflects visibility rules.

The expected output section correctly matches the behavior defined by OOP visibility rules:

  1. Only public inner classes are accessible from global scope
  2. Only public inner classes are accessible from unrelated classes
  3. Public and protected inner classes are accessible from child classes

This ensures the PHP implementation correctly enforces these visibility constraints for inner classes.

Zend/zend_vm_execute.h (4)

21557-21574: Repeated logic detected.

This code segment repeats the same scoping checks and error handling as the earlier segment. Refer to the previous comment at lines 10802-10819.


30051-30068: Repeated logic detected.

This code segment repeats the same scoping checks and error handling as the earlier segment. Refer to the previous comment at lines 10802-10819.


37859-37876: Repeated logic detected.

This code segment repeats the same scoping checks and error handling as the earlier segment. Refer to the previous comment at lines 10802-10819.


50678-50695: Repeated logic detected.

This code segment repeats the same scoping checks and error handling as the earlier segment. Refer to the previous comment at lines 10802-10819.

Zend/zend_vm_def.h (2)

4441-4445: Ensure consistent enforcement of method visibility.

This logic correctly blocks a protected method from returning a private class outside the same scope. However, confirm whether you need equivalent checks for other visibility modifiers (e.g., private methods returning deeper scope classes) to maintain consistent enforcement.


4448-4450: Good fallback for type enforcement.

The fallback call to zend_check_type_slow enforces the declared return type. Throwing an error ensures the engine remains consistent with user-declared type expectations.

Zend/zend_API.c (2)

1819-1827: Check scope precondition handling logic carefully.
This newly added code properly checks whether the current scope (zend_get_executed_scope()) is null, but it imposes a hard rule that instantiation must not happen from the global scope for classes requiring a specific scope. This could break existing code if classes with required_scope were formerly instantiated from the global scope. If that is intentional, it might be worth clarifying this restriction in documentation or error messages.

Would you like a script to scan your codebase to collect all sites where these classes could be instantiated? I can provide it if you want to confirm there's no accidental usage from the global scope.


1828-1835: Clarify the “absolute” versus “lexical” scope checks.
When required_scope_absolute is set, lines 1829-1834 strictly validate that the executed or lexical scope matches exactly class_type->required_scope. This is stricter than an inheritance check and might lead to unexpected failures if the lexical scope is slightly different but still suitable in a broader sense. Consider documenting the exact intended usage for “absolute” scope.

Please confirm that the design explicitly needs an exact scope match. Otherwise, a broader check such as instanceof_function(scope, required_scope) might be appropriate.

Comment on lines +4431 to +4440
if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) {
if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) {
if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) {
zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));
HANDLE_EXCEPTION();
} else {
zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));
HANDLE_EXCEPTION();
}
} else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extracting repeated type error logic into a helper function.

You have a repeated pattern for throwing a type error for public methods returning private or protected classes (lines 4434–4439). Extracting this into a helper function would reduce duplication and streamline future maintenance.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/classes/inner_classes/inheritance.phpt (1)

1-21: Test should have expected output

The test is validating that the circular class dependency resolution works correctly, but doesn't have any expected output. This makes it unclear what behavior is being verified beyond "doesn't crash".

Consider adding some instantiation or usage of these classes with explicit assertions to make the test more meaningful. For example, test that the class hierarchy is correctly established by checking instanceof relationships.

Zend/zend.h (1)

167-222: Consider adding documentation for new class entry fields

While the new fields follow the coding style of the rest of the file (which has minimal inline documentation), adding brief comments explaining the purpose of these new fields would help future developers understand the nested class implementation.

Example:

+ /* Scope in which this class must be accessed */
 zend_class_entry *required_scope;
+ /* Lexical scope where this class is defined */
 zend_class_entry *lexical_scope;
+ /* Flag indicating if required_scope is absolute */
 bool required_scope_absolute;

...

+ /* Number of classes nested within this class */
 uint32_t num_nested_classes;

...

+ /* Array of class entries for classes nested within this class */
 zend_class_entry **nested_classes;
Zend/zend_compile.c (1)

9114-9148: Deferring and scanning nested classes
Implementation logic for zend_defer_class_decl (to queue declarations) and zend_scan_nested_class_decl (to recursively discover nested classes) appears robust.
One minor nitpick: reusing the variable name ast inside the loop could reduce clarity. Consider renaming the loop variable to avoid overshadowing.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64f1bb5 and ee24baa.

📒 Files selected for processing (8)
  • Zend/zend.h (3 hunks)
  • Zend/zend_compile.c (18 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_opcode.c (3 hunks)
  • tests/classes/inner_classes/inheritance.phpt (1 hunks)
  • tests/classes/inner_classes/resolution_001.phpt (1 hunks)
  • tests/classes/inner_classes/resolution_002.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_005.phpt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • Zend/zend_opcode.c
  • tests/classes/inner_classes/simple_declaration_005.phpt
  • Zend/zend_namespaces.c
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: BENCHMARKING
  • GitHub Check: FREEBSD
🔇 Additional comments (23)
tests/classes/inner_classes/resolution_001.phpt (1)

1-16: LGTM: Test effectively verifies basic inheritance resolution for nested classes

The test correctly verifies that an inner class can extend a class from the outer scope, and that instances of the inner class are recognized as instances of the parent class through the instanceof operator.

tests/classes/inner_classes/resolution_002.phpt (1)

1-22: LGTM: Test thoroughly verifies class name shadowing behavior

This test correctly verifies two important scenarios:

  1. An inner class can extend a shadowed class within its outer class scope
  2. An inner class can extend a class from an external namespace using fully qualified names

The expected output confirms that inheritance works correctly in both shadowing scenarios.

tests/classes/inner_classes/inheritance.phpt (1)

14-15: Add explicit verification of parent class resolution

The test doesn't explicitly verify that the unqualified class name in Inner2 extends Other correctly resolves to Outer\Other rather than looking for an Other class in the global namespace.

Add an explicit assertion with get_parent_class() to verify the parent class resolution:

 class Outer {
  class Middle extends Outer\Other {
      class Inner1 extends Inner2 {}
      class Inner2 extends Other {}
+     
+     public function test() {
+         echo get_parent_class(Inner2::class), "\n";
+     }
  }
  abstract class Other {}
 }
+
+$m = new Outer\Middle();
+$m->test();

Then add to the expected output:

Outer\Other
Zend/zend.h (3)

167-170: LGTM: New scope-related class entry fields for nested classes

These fields support the nested class feature by tracking the scope relationships between parent and nested classes:

  • required_scope: The scope in which a class must be accessed
  • lexical_scope: The lexical context where a class is defined
  • required_scope_absolute: Flag to indicate if the required scope is absolute

This enables proper visibility control and name resolution for nested classes.


213-213: LGTM: Added counter for nested classes

Adding a counter for nested classes is necessary to properly track the number of nested class entries.


222-222: LGTM: Added array of nested class entries

The nested_classes array properly complements the num_nested_classes counter to maintain references to all nested classes defined within a parent class.

Zend/zend_compile.c (17)

41-41: Include of zend_namespaces.h
No issues found with adding this header. It appears necessary for handling the new nested-class functionality.


428-428: Initialize the nested_class_queue
Setting CG(nested_class_queue) = NULL; is a proper way to ensure a clean initial state for the queue.


898-912: Extending modifiers for nested classes
Allowing T_READONLY, T_ABSTRACT, and T_FINAL on nested classes aligns well with the existing design. No conflicts or redundancies spotted.


949-950: Introducing "nested class" as a member type
Mapping the ZEND_MODIFIER_TARGET_NESTED_CLASS value to "nested class" for error messaging is clear and consistent.


1058-1088: Restricting certain modifiers on nested classes
Disallowing multiple access-level modifiers, static, and explicit public/protected/private set variants for nested classes prevents conflicting visibility or scope issues. This approach appears consistent with your design goals.


1183-1207: zend_resolve_class_in_scope function
This new function systematically resolves class names within lexical scopes. The memory handling logic (returning the newly allocated string if found) is correct for the typical Zend string lifecycle. No leaks detected.


1267-1272: Nested scope resolution fallback
If CG(active_class_entry) is present, the code tries to resolve the name within that scope; otherwise, it falls back to zend_prefix_with_ns. This incremental approach is clear.


2176-2177: Initializing nested class counters
Setting ce->num_nested_classes = 0 and ce->nested_classes = NULL is a proper default initialization. Please confirm that memory for ce->nested_classes is consistently freed to avoid leaks.


9054-9054: New helper for interfaces
Defining the static function zend_compile_implements is a straightforward extension for interface handling. Its usage appears coherent with the rest of the file.


9223-9224: Anonymous class scope
For anonymous classes, setting ce->required_scope = NULL is correct, as they typically do not inherit direct visibility constraints.


9266-9268: Implements clause for nested classes
The call to zend_compile_implements(implements_ast, ce) when inside a nested class ensures that interfaces are properly parsed. No concerns here.


9284-9286: Scanning nested classes in namespace scope
zend_scan_nested_class_decl(stmt_ast, name) when ce->lexical_scope->type == ZEND_NAMESPACE_CLASS ensures deeper discovery of multiple layers of nested classes.


9316-9316: Goto label usage
Jumping to compile_nested_classes on successful early binding is a logical flow control for deferred compilation.


9325-9325: Goto label usage (alternate path)
Ensuring the same jump to compile_nested_classes after linking is consistent with the new compilation flow for nested classes.


9395-9412: Flushing the deferred nested_class_queue
Extracting the queued nested class ASTs and compiling them after the outer class is finalized is a clean technique for deferring. Properly destroying the queue prevents memory leaks.


11703-11706: Defer the class declaration if inside a class
Moving class declarations to a separate deferred compilation phase when encountered inside another class eliminates scoping conflicts and clarifies the compilation order.


9175-9205:

✅ Verification successful

Applying modifiers and setting up lexical scope
Reallocating ce->lexical_scope->nested_classes for newly declared nested classes is a sound approach. Please confirm that all references to nested_classes are properly cleaned up at destruction time.


🏁 Script executed:

#!/bin/bash
# Searching the codebase for references to free or destroy `nested_classes`.
# This helps confirm that nested class arrays are managed properly.
rg -A 20 -B 20 "nested_classes"

Length of output: 13246


Nested Classes Memory Management Confirmed

The modifications for setting up ce->lexical_scope->nested_classes are sound. A search in the codebase shows that the allocated array is properly freed via efree(ce->nested_classes) in Zend/zend_opcode.c, which confirms that cleanup at destruction time is handled correctly. Please ensure that any error or exceptional paths also trigger this cleanup.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
Zend/zend_namespaces.c (3)

42-44: ⚠️ Potential issue

Potential off-by-one error in namespace parsing

The loop condition while (pos <= end) could lead to reading one byte past the end of the string. Since end points to the null terminator, the condition should be while (pos < end).

-while (pos <= end) {
-   if (pos == end || *pos == '\\') {
+while (pos < end) {
+   if (*pos == '\\') {

45-46: ⚠️ Potential issue

Incorrect namespace segment extraction

The string initialization is using ZSTR_VAL(lc_name) instead of using start (beginning of the current segment). This results in creating namespace prefixes instead of individual segments.

-zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
+zend_string *needle = zend_string_init(start, len, 0);
+needle = zend_string_tolower(needle);

58-60: ⚠️ Potential issue

Update start pointer after segment processing

After processing a namespace segment, the start pointer should be updated to point to the beginning of the next segment.

         zend_string_release(needle);

         parent_ns = ns;
+        start = pos + 1;
      }
      pos ++;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee24baa and f5dee5d.

📒 Files selected for processing (1)
  • Zend/zend_namespaces.c (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: BENCHMARKING
  • GitHub Check: LINUX_X32_DEBUG_ZTS
🔇 Additional comments (4)
Zend/zend_namespaces.c (4)

92-95: Add NULL check in zend_lookup_namespace

The function should check if name is NULL to avoid potential crashes. This would also make it consistent with zend_resolve_namespace.

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+      return NULL; // or return EG(global_namespace) for consistency
+   }
    zend_string *lc_name = zend_string_tolower(name);
    zend_class_entry *ns = zend_hash_find_ptr(EG(namespaces), lc_name);
    zend_string_release(lc_name);

24-32: Memory allocation in create_namespace is now correct

The memory allocation size issue has been fixed, now correctly allocating sizeof(zend_class_entry) instead of sizeof(zend_class_entry *). Good job!


67-90: zend_resolve_namespace implementation looks good

The implementation of zend_resolve_namespace is robust:

  • It initializes the global namespace if needed
  • Properly handles NULL or empty namespace names
  • Correctly manages string references

100-107: Clean namespace destruction implementation

The zend_destroy_namespaces function properly checks if the namespaces hash table exists before attempting to destroy it, and correctly resets the global namespace pointer. This prevents potential use-after-free issues.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
Zend/zend_namespaces.c (1)

54-54: ⚠️ Potential issue

Incorrect hash table key for namespace insertion

When adding a new namespace, the function uses lc_name as the key, which is the lowercase version of the entire name, not just the segment. This can break lookups for nested namespaces:

-zend_hash_add_ptr(EG(namespaces), lc_name, ns);
+zend_hash_add_ptr(EG(namespaces), needle, ns);
🧹 Nitpick comments (2)
Zend/zend_compile.c (2)

9311-9311: Use of goto compile_nested_classes.
Control flow via goto is consistent with other parts of the engine, though more structured approaches could improve clarity.


9320-9320: Repeat usage of goto compile_nested_classes.
Again, consider removing goto if a refactor does not break existing patterns; otherwise, it’s acceptable per Zend Engine conventions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5dee5d and fefb92c.

📒 Files selected for processing (4)
  • Zend/zend.h (1 hunks)
  • Zend/zend_compile.c (17 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_opcode.c (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Zend/zend.h
  • Zend/zend_opcode.c
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (19)
Zend/zend_compile.c (15)

41-41: Header inclusion looks good.
Including zend_namespaces.h appears necessary for the new namespace-related operations.


428-428: Initialization of CG(nested_class_queue).
Setting this global to NULL during initialization is consistent with its subsequent usage.


897-916: Properly extend modifier tokens to handle nested classes.
Enabling readonly, abstract, and final for inner classes looks correct. Consider adding thorough tests to ensure no conflicting modifiers (e.g., final + abstract) are allowed inadvertently.


949-950: String representation is consistent.
Defining "nested class" for the error message is straightforward and consistent with the existing pattern.


1058-1088: Guarding disallowed modifiers on nested classes.
Repeated checks disallow static, multiple access modifiers, and public/protected/private “set”. If this is deliberate by design, ensure it’s clearly documented in the RFC/spec, as it restricts nested classes more than typical top-level classes.


1183-1207: Resolving class references within nested scopes.
The string concatenation approach to walk up lexical_scope looks correct. Kindly verify there’s no performance concern for deep nesting, and confirm memory is released properly on each iteration.


1267-1272: Short-circuit resolution within active class scope.
Returning the resolved nested class name early if found is an efficient fallback strategy.


9052-9052: zend_compile_implements usage.
Defining this static function to handle nested class interfaces is consistent.


9112-9147: Deferred parsing and scanning of nested classes.
The deferral mechanism, plus recursively scanning child AST_CLASS nodes, is logical. Consider tests for deeply nested classes or cyclical references to validate this logic.


9174-9195: Applying nested-class modifiers and scope logic.
The interplay between ce->required_scope, ce->required_scope_absolute, and ce->lexical_scope is a solid approach for access control. Double-check coverage for transitions between private/protected scope within multiple levels of nesting.


9218-9219: Nullifying required_scope on anonymous classes.
Clearing the scope is correct for anonymous classes. No concerns here.


9261-9263: Implements interface for nested classes.
Compiling interfaces for nested classes is straightforward. Ensure we have a test scenario for nested classes that implement interfaces.


9279-9281: Scan nested classes within a namespace scope.
This condition ensures nested classes are recognized if inside a namespace-level class.


9390-9407: Compiling deferred nested classes.
The deferred queue ensures nested classes are compiled after the parent. This is a valid approach. Confirm that exceptions or errors raised during nested class compilation are handled properly.


11698-11701: Defer class declaration for inner classes in an active class.
Deferring the compilation when CG(active_class_entry) is set aligns with the new nested class model.

Zend/zend_namespaces.c (4)

24-32: Memory allocation size in create_namespace is correct.

Previously, there was a concern about using sizeof(zend_class_entry *) instead of sizeof(zend_class_entry). The code here correctly uses sizeof(zend_class_entry), resolving that issue.


42-43: Potential off-by-one error in while (pos <= end)

This logic was raised in a previous review; using while (pos <= end) can lead to reading one byte past the end of the string. The condition should typically be while (pos < end) to avoid overshooting the boundary:

-while (pos <= end) {
-   if (pos == end || *pos == '\\') {
+while (pos < end) {
+   if (*pos == '\\') {

45-45: Incorrect namespace segment extraction

As previously noted, constructing needle with ZSTR_VAL(lc_name) instead of using the substring segment (start) may lead to building partial or incorrect segments:

-zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
+zend_string *needle = zend_string_init(start, len, 0);

59-60: start pointer is not updated after processing the current segment

This comment was mentioned before: after extracting a segment, you should adjust start to move past the backslash:

zend_string_release(needle);

parent_ns = ns;
+start = pos + 1;

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
Zend/zend_namespaces.c (1)

92-98: 🛠️ Refactor suggestion

Add a NULL check in zend_lookup_namespace

To avoid potential crashes when name is NULL, mirror the checks used in zend_resolve_namespace:

 zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+       return EG(global_namespace);
+   }
    zend_string *lc_name = zend_string_tolower(name);
    ...
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16bc9c1 and 4a1ee2d.

📒 Files selected for processing (2)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_opcode.c (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Zend/zend_opcode.c
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
  • GitHub Check: LINUX_X32_DEBUG_ZTS
🔇 Additional comments (4)
Zend/zend_namespaces.c (4)

1-23: License and header includes look fine
No issues found with the licensing block and includes.


24-32: Memory allocation for create_namespace is correct
The use of pemalloc(sizeof(zend_class_entry), 0) properly allocates memory for the entire zend_class_entry structure.


67-90: Global namespace creation logic is sound
The function handles empty or NULL names by returning the global namespace, which aligns well with typical namespace resolution strategies.


100-107: Memory cleanup in zend_destroy_namespaces
Graceful teardown of namespace structures and resetting globals looks correct.

Comment on lines 34 to 65
static zend_class_entry *insert_namespace(const zend_string *name, zend_string *lc_name) {
zend_class_entry *parent_ns = EG(global_namespace);
zend_class_entry *ns = parent_ns;
const char *start = ZSTR_VAL(name);
const char *end = start + ZSTR_LEN(name);
const char *pos = start;
size_t len = 0;

while (pos <= end) {
if (pos == end || *pos == '\\') {
len = pos - start;
zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);

ns = zend_hash_find_ptr(EG(namespaces), needle);

if (!ns) {
zend_string *full_name = zend_string_init(ZSTR_VAL(name), len, 1);
zend_string *interned_name = zend_new_interned_string(full_name);
ns = create_namespace(interned_name);
ns->lexical_scope = parent_ns;
zend_hash_add_ptr(EG(namespaces), needle, ns);
zend_string_release(full_name);
}
zend_string_release(needle);

parent_ns = ns;
}
pos ++;
}

return ns;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix substring extraction and pointer offset in insert_namespace

This loop incorrectly uses ZSTR_VAL(lc_name) without accounting for the offset when creating each segment substring. Additionally, start is never updated to parse subsequent segments, causing improper namespace segment handling. A more robust approach is to track each segment boundary accurately and handle the trailing segment after the loop. For example:

-while (pos <= end) {
-    if (pos == end || *pos == '\\') {
-        len = pos - start;
-        zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
-        ...
-    }
-    pos++;
+while (pos < end) {
+    if (*pos == '\\') {
+        len = pos - start;
+        zend_string *needle = zend_string_init(
+            ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name)),
+            len,
+            0
+        );
+        ...
+        zend_string_release(needle);
+        parent_ns = ns;
+        start = pos + 1; // Update pointer to the next segment
+    }
+    pos++;
+}
// Handle the trailing segment if the name doesn't end in '\'
+if (start < end) {
+    len = end - start;
+    zend_string *needle = zend_string_init(
+        ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name)),
+        len,
+        0
+    );
+    ...
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static zend_class_entry *insert_namespace(const zend_string *name, zend_string *lc_name) {
zend_class_entry *parent_ns = EG(global_namespace);
zend_class_entry *ns = parent_ns;
const char *start = ZSTR_VAL(name);
const char *end = start + ZSTR_LEN(name);
const char *pos = start;
size_t len = 0;
while (pos <= end) {
if (pos == end || *pos == '\\') {
len = pos - start;
zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
ns = zend_hash_find_ptr(EG(namespaces), needle);
if (!ns) {
zend_string *full_name = zend_string_init(ZSTR_VAL(name), len, 1);
zend_string *interned_name = zend_new_interned_string(full_name);
ns = create_namespace(interned_name);
ns->lexical_scope = parent_ns;
zend_hash_add_ptr(EG(namespaces), needle, ns);
zend_string_release(full_name);
}
zend_string_release(needle);
parent_ns = ns;
}
pos ++;
}
return ns;
}
static zend_class_entry *insert_namespace(const zend_string *name, zend_string *lc_name) {
zend_class_entry *parent_ns = EG(global_namespace);
zend_class_entry *ns = parent_ns;
const char *start = ZSTR_VAL(name);
const char *end = start + ZSTR_LEN(name);
const char *pos = start;
size_t len = 0;
while (pos < end) {
if (*pos == '\\') {
len = pos - start;
zend_string *needle = zend_string_init(
ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name)),
len,
0
);
ns = zend_hash_find_ptr(EG(namespaces), needle);
if (!ns) {
zend_string *full_name = zend_string_init(
ZSTR_VAL(name) + (start - ZSTR_VAL(name)),
len,
1
);
zend_string *interned_name = zend_new_interned_string(full_name);
ns = create_namespace(interned_name);
ns->lexical_scope = parent_ns;
zend_hash_add_ptr(EG(namespaces), needle, ns);
zend_string_release(full_name);
}
zend_string_release(needle);
parent_ns = ns;
start = pos + 1; // Update pointer to the next segment
}
pos++;
}
// Handle the trailing segment if the name doesn't end in '\'
if (start < end) {
len = end - start;
zend_string *needle = zend_string_init(
ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name)),
len,
0
);
ns = zend_hash_find_ptr(EG(namespaces), needle);
if (!ns) {
zend_string *full_name = zend_string_init(
ZSTR_VAL(name) + (start - ZSTR_VAL(name)),
len,
1
);
zend_string *interned_name = zend_new_interned_string(full_name);
ns = create_namespace(interned_name);
ns->lexical_scope = parent_ns;
zend_hash_add_ptr(EG(namespaces), needle, ns);
zend_string_release(full_name);
}
zend_string_release(needle);
parent_ns = ns;
}
return ns;
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
Zend/zend_namespaces.c (1)

42-61: ⚠️ Potential issue

Reiterate existing substring extraction bug for nested namespaces.

This block reintroduces a previously flagged issue: using ZSTR_VAL(lc_name) unconditionally ignores the updated start pointer for each new segment. Consequently, multiple-segment namespaces like "Foo\Bar" will parse incorrectly, as both segments derive from the full string's start. This renders nested namespaces indistinguishable.

Proposed fix to incorporate the offset:

 while (pos <= end) {
   if (pos == end || *pos == '\\') {
     len = pos - start;
-    zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
+    zend_string *needle = zend_string_init(
+      ZSTR_VAL(lc_name) + ((start - ZSTR_VAL(name)) /* offset */),
+      len,
+      0
+    );
     ...
     zend_string_release(needle);
     parent_ns = ns;
   }
   pos++;
 }
🧹 Nitpick comments (1)
Zend/zend_namespaces.c (1)

99-106: Consider adding doc comments or inline notes.

This file introduces new API functions (e.g., create_namespace, insert_namespace, etc.), but lacks descriptive doc comments that would aid maintainability. Documenting parameters and expected behaviors will help future contributors.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a1ee2d and faf1255.

📒 Files selected for processing (1)
  • Zend/zend_namespaces.c (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: BENCHMARKING
🔇 Additional comments (2)
Zend/zend_namespaces.c (2)

25-25: Correct memory allocation approach.

Thank you for addressing the memory allocation size concern by using sizeof(zend_class_entry) instead of sizeof(zend_class_entry*). This aligns with previous feedback and ensures that the structure is allocated correctly.


91-97: Add null-check for namespace lookups.

This was also highlighted previously. The function zend_lookup_namespace(zend_string *name) lacks a NULL guard. Passing a NULL name will invoke zend_string_tolower on a null pointer, potentially causing a crash.

Would you confirm if any usage path can supply a NULL name? If so, please add:

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+    if (name == NULL) {
+       return NULL; // or handle it consistently with zend_resolve_namespace
+    }
     zend_string *lc_name = zend_string_tolower(name);
     ...
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
Zend/zend_namespaces.c (1)

90-93: 🛠️ Refactor suggestion

Add NULL check in zend_lookup_namespace.
If a caller inadvertently passes a NULL name, zend_string_tolower(name) will cause undefined behavior. A simple guard will ensure consistency with the null-handling approach in zend_resolve_namespace.

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+       return EG(global_namespace);
+   }
    zend_string *lc_name = zend_string_tolower(name);
    ...
}
🧹 Nitpick comments (1)
Zend/zend_namespaces.c (1)

34-63: Consider adding coverage for multi-segment namespaces to test suite.
No public test currently ensures that multi-segment namespaces (e.g., Foo\Bar\Baz) are inserted or resolved properly. Adding a dedicated test will help detect issues caused by the missing offset update and confirm correct parsing.

Would you like help creating a targeted test to verify nested namespaces?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between faf1255 and 36b7f6b.

📒 Files selected for processing (1)
  • Zend/zend_namespaces.c (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (3)
Zend/zend_namespaces.c (3)

24-32: Correct fix applied to memory allocation in create_namespace.
This implementation correctly allocates the size of the zend_class_entry struct, fixing the previously reported issue of allocating only pointer size.


42-44: Potential off-by-one error in parsing loop.
Using while (pos <= end) risks a read of *pos when pos equals end. Although this is currently safe due to short-circuit in the if (pos == end || *pos == '\\') check, switching the loop condition to while (pos < end) would be cleaner and match conventional parsing logic.

-while (pos <= end) {
+while (pos < end) {
    if (pos == end || *pos == '\\') {
        ...
    }
    pos++;
}

Also applies to: 59-60


45-53: Incorrect substring extraction due to unadjusted offset.
As previously noted, this code uses ZSTR_VAL(lc_name) without incorporating the start pointer offset, which prevents correct segmentation of multi-part namespaces (e.g., A\B\C). You must initialize the substring using (ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name))). Also, update start after finishing each segment to progress correctly through the string.

- zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
+ zend_string *needle = zend_string_init(
+    ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name)),
+    len,
+    0
+);
...
- parent_ns = ns;
+ parent_ns = ns;
+ start = pos + 1;

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
Zend/zend_compile.c (1)

1058-1088: Consolidate repeated checks for better maintainability
Your checks disallow multiple access modifiers, static, and different visibility statuses on nested classes. This is correct per your RFC, but consider consolidating these repeated if-statements for clarity and to reduce duplication.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e21d0b1 and a505fe5.

📒 Files selected for processing (4)
  • Zend/zend.h (1 hunks)
  • Zend/zend_compile.c (17 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_opcode.c (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Zend/zend.h
  • Zend/zend_opcode.c
🧰 Additional context used
🧬 Code Definitions (2)
Zend/zend_namespaces.c (1)
Zend/zend_namespaces.h (3)
  • name (25-25)
  • name (26-26)
  • zend_destroy_namespaces (27-27)
Zend/zend_compile.c (3)
Zend/zend_compile.h (1)
  • name (988-988)
Zend/zend_namespaces.h (2)
  • name (25-25)
  • name (26-26)
Zend/zend_namespaces.c (1)
  • zend_resolve_namespace (65-88)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (23)
Zend/zend_compile.c (16)

41-41: Include header for namespace resolution
By including zend_namespaces.h, you can now directly invoke functions like zend_resolve_namespace() or zend_lookup_namespace(). No issues found.


428-428: Initialize nested_class_queue
Setting CG(nested_class_queue) = NULL at startup is appropriate. It’s later allocated and freed in the added logic, preventing leaks.


898-912: Support modifiers for nested classes
Allowing readonly, abstract, and final on nested classes follows the intended RFC approach. The checks appear consistent and correctly mirror existing logic for standard classes.


949-950: Label nested class target
Defining the target string as "nested class" clarifies error messages and debugging. No concerns here.


1183-1207: Add new scope resolution function
zend_resolve_class_in_scope() gracefully handles concatenation and cleanup. The string management is handled correctly, with no memory leaks evident.


1267-1272: Fallback to nested scope resolution
Checking CG(active_class_entry) before invoking zend_resolve_class_in_scope() is a logical approach and keeps lookups neatly constrained.


9052-9052: Implements interface compilation
Introduction of zend_compile_implements is consistent with the existing patterns. No immediate issues.


9112-9127: Deferred class declaration for nested classes
Storing nested classes in CG(nested_class_queue) until main class compilation finishes avoids premature linking. The data structure usage is correct.


9174-9195: Merge nested-class flags
Applying final, abstract, or readonly inherited flags and managing scope references is consistent with the proposed RFC. No problems spotted.


9218-9220: Set lexical scope for top-level classes
Assigning ce->lexical_scope to CG(active_class_entry) or the current namespace ensures proper resolution of parent contexts.


9261-9263: Compile implemented interfaces
Invoking zend_compile_implements(ast, ce) here correctly slots into the class compilation flow.


9279-9281: Scan nested declarations
Calling zend_scan_nested_class_decl(stmt_ast, name) under a namespaced parent is a neat approach. Consider adding a null-check if any future code allows an empty ce->lexical_scope.


9311-9311: Jump to nested class compilation
Use of goto compile_nested_classes; is consistent with the engine’s control flow style.


9320-9320: Finalize linking and compile nested classes
Same pattern of using goto to unify logic. No concerns here.


9390-9408: Compile queued nested classes
Dequeueing and compiling all pending nested classes before destroying the queue ensures everything is fully processed. No memory leaks spotted.


11698-11701: Defer or compile nested classes conditionally
Deferring the class declaration if another class is active aligns with the new approach. Otherwise, compilation proceeds immediately.

Zend/zend_namespaces.c (7)

24-32: Allocation approach looks good in create_namespace.

This correctly allocates the size of the zend_class_entry structure to avoid any pointer-size mismatch.


42-42: Potential off-by-one error in insert_namespace.

This loop condition while (pos <= end) can lead to reading one byte past the end of the string. It should typically be while (pos < end) to prevent out-of-bounds access.

Apply this diff to fix the condition:

-while (pos <= end) {
+while (pos < end) {

45-45: Incorrect substring extraction for namespace segments.

Using ZSTR_VAL(lc_name) instead of start when creating needle inadvertently initializes the substring from the beginning of the entire lowercase name rather than from the start pointer of the current segment.

Apply this diff to correctly extract the segment:

-zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0);
+zend_string *needle = zend_string_init(start, len, 0);

57-59: Missing update of start pointer after processing each segment.

After handling a segment, start should be incremented to pos + 1 before moving on to the next segment to avoid creating overlapping segments.

Apply this diff to ensure each namespace segment is processed correctly:

         zend_string_release(needle);

         parent_ns = ns;
+        start = pos + 1;
      }
      pos++;

65-88: No immediate issues in zend_resolve_namespace.

The checks for a null or empty name are clear, and the logic to create and cache the global namespace if absent looks correct.


90-90: Add NULL check in zend_lookup_namespace.

Similar to an earlier review, passing NULL as the function argument can cause a crash when calling zend_string_tolower(name). It's safer to return NULL or the global namespace if name is NULL.

Apply this diff to guard against NULL:

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (name == NULL) {
+       return NULL; 
+   }
    zend_string *lc_name = zend_string_tolower(name);
    ...
}

98-105: zend_destroy_namespaces implementation is clear.

This function properly destroys the hash table and resets the global pointers, avoiding memory leaks.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/classes/inner_classes/aliases.phpt (1)

1-19: Consider adding additional test cases for other visibility modifiers.

The current test verifies that private inner classes cannot be instantiated via aliases from the global scope. Consider expanding the test suite to include cases for protected inner classes and public inner classes to ensure comprehensive coverage of visibility behaviors with aliases.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a505fe5 and 68f5063.

📒 Files selected for processing (1)
  • tests/classes/inner_classes/aliases.phpt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: BENCHMARKING
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X32_DEBUG_ZTS
🔇 Additional comments (5)
tests/classes/inner_classes/aliases.phpt (5)

1-2: LGTM: Clear and descriptive test title.

The test title "aliases cannot escape" clearly describes the test's purpose of verifying that class aliases cannot bypass visibility restrictions.


6-8: This test correctly sets up the visibility constraint scenario.

The code defines an Outer class with a private inner class Inner, which establishes the baseline for testing visibility restrictions. This is a good test case as it verifies one of the key aspects of inner class encapsulation.


10-10: LGTM: Good test for alias behavior with inner classes.

This line correctly creates an alias for the inner class, which is essential for testing whether visibility restrictions can be bypassed through aliases. This is an important security consideration for the nested classes feature.


12-12: LGTM: Good test for attempted instantiation.

This line properly tests whether instantiating the aliased class will enforce the visibility restrictions of the original inner class.


15-18: LGTM: Correct expected error output.

The expected output accurately captures the TypeError that should be thrown when trying to instantiate a private inner class from the global scope, even through an alias. The %s and %d placeholders are correctly used for file path and line number.

withinboredom pushed a commit that referenced this pull request Mar 29, 2025
```
ext/gd/libgd/gd.c:2275:14: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
    #0 0x5d6a2103e1db in php_gd_gdImageCopy /home/dcarlier/Contribs/php-src/ext/gd/libgd/gd.c:2275
    #1 0x5d6a210a2b63 in gdImageCrop /home/dcarlier/Contribs/php-src/ext/gd/libgd/gd_crop.c:57
    #2 0x5d6a21018ca4 in zif_imagecrop /home/dcarlier/Contribs/php-src/ext/gd/gd.c:3575
    #3 0x5d6a21e46e7a in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER /home/dcarlier/Contribs/php-src/Zend/zend_vm_execute.h:1337
    #4 0x5d6a221188da in execute_ex /home/dcarlier/Contribs/php-src/Zend/zend_vm_execute.h:57246
    #5 0x5d6a221366bd in zend_execute /home/dcarlier/Contribs/php-src/Zend/zend_vm_execute.h:61634
    #6 0x5d6a21d107a6 in zend_execute_scripts /home/dcarlier/Contribs/php-src/Zend/zend.c:1895
    #7 0x5d6a21a63409 in php_execute_script /home/dcarlier/Contribs/php-src/main/main.c:2529
    #8 0x5d6a22516d5e in do_cli /home/dcarlier/Contribs/php-src/sapi/cli/php_cli.c:966
    php#9 0x5d6a2251981d in main /home/dcarlier/Contribs/php-src/sapi/cli/php_cli.c:1341
    php#10 0x7f10d002a3b7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    php#11 0x7f10d002a47a in __libc_start_main_impl ../csu/libc-start.c:360
    php#12 0x5d6a20a06da4 in _start (/home/dcarlier/Contribs/php-src/sapi/cli/php+0x2806da4) (BuildId: d9a79c7e0e4872311439d7313cb3a81fe04190a2)
```

close phpGH-18006
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
Zend/zend_namespaces.c (2)

36-65: ⚠️ Potential issue

Segments are not split correctly for nested namespaces.

This code never advances the start pointer, and always uses ZSTR_VAL(lc_name) instead of the substring offset. As a result, all segments are derived from the beginning of the string, breaking namespace parsing for multi-segment names. This issue was raised in a previous review but remains unaddressed.

Below is a minimal fix that updates both the pointer offset and start after handling a segment:

 while (pos <= end) {
     if (pos == end || *pos == '\\') {
-        zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), pos - start, 0);
+        size_t len = pos - start;
+        zend_string *needle = zend_string_init(
+            ZSTR_VAL(lc_name) + (start - ZSTR_VAL(name)),
+            len,
+            0
+        );

         ...

+        start = pos + 1; // Update pointer to parse the next segment
     }
     pos++;
 }

92-98: ⚠️ Potential issue

Add a null check to prevent potential crashes.

If zend_lookup_namespace is called with name == NULL, this line will dereference a null pointer in zend_string_tolower(name). A past comment also highlighted the need for this guard.

Consider:

zend_class_entry *zend_lookup_namespace(zend_string *name) {
+   if (!name) {
+       // Decide on an appropriate fallback, e.g. returning EG(global_namespace) or NULL
+       return NULL;
+   }
    zend_string *lc_name = zend_string_tolower(name);
    ...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2103df0 and d02601f.

📒 Files selected for processing (2)
  • Zend/zend_API.c (1 hunks)
  • Zend/zend_namespaces.c (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
Zend/zend_namespaces.c (1)
Zend/zend_namespaces.h (3)
  • name (25-25)
  • name (26-26)
  • zend_destroy_namespaces (27-27)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
🔇 Additional comments (8)
Zend/zend_API.c (4)

1819-1821: Initialize local variables carefully to avoid confusion.

The introduction of check_class and scope near the top of this block is clear and straightforward. Just ensure that both will be updated correctly if subsequent checks temporarily modify check_class or if zend_get_executed_scope() yields unexpected values in certain engine paths.


1822-1829: Improve null-scope handling for required-scope classes.

When scope == NULL, the error message and subsequent returns are correctly placed. The logic properly nullifies the return object and stops execution. This robustly ensures that a class with required_scope cannot be instantiated from truly global scope.


1830-1837: Ensure consistent fail-fast behavior for private classes.

For classes marked as required_scope_absolute, the code properly checks for exact scope matches (including lexical scope) and cleanly halts with a type error if there's a mismatch, preventing further instantiation steps. This aligns with the intended “private class” semantics.


1842-1845: Guard against unforeseen lexical scope loops.

This loop re-checks check_class->lexical_scope. While the code looks correct for typical nested classes, ensure that no unusual nesting or recursion can bypass the check. If a cycle were hypothetically introduced, a fail-safe may be needed. For now, your usage of a goto label is consistent with Zend Engine style, but a cautious eye on potential loops is advised.

Would you like me to generate a script to search for any references where lexical scopes might form a cycle or appear multiple times, to confirm no infinite loops can occur?

Zend/zend_namespaces.c (4)

1-18: No issues or actionable feedback for the license header.


24-34: Verify handling of name pointer.

Currently, this function stores name in ns->name but does not copy or increment its reference count. If the caller does not supply an interned or otherwise guaranteed-persistent string, this could lead to use of freed memory.

Please confirm whether the name parameter is always interned or if it needs additional safety measures (e.g., zend_string_copy(name)).


67-90: Namespace resolution logic looks good.

The function properly initializes the global namespace if needed and handles empty or NULL names. The rest of the logic for finding or creating the namespace is straightforward and meets expectations.


100-107: Global namespace cleanup is correct.

The function properly destroys the namespaces hash table and resets EG(namespaces) and EG(global_namespace) to NULL.

Comment on lines +1838 to +1839
zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Return after throwing the type error for protected classes.

Currently, the code calls zend_type_error() at line 1838 but does not set arg to NULL or return immediately. The engine will still throw an exception, but for parallelism with the private-class block, consider returning right away to keep the logic uniform and prevent any unexpected continuation of object creation.

Apply this diff to mirror the private-class pattern:

-            zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
+            zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
+            ZVAL_NULL(arg);
+            Z_OBJ_P(arg) = NULL;
+            return FAILURE;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
}
zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
ZVAL_NULL(arg);
Z_OBJ_P(arg) = NULL;
return FAILURE;
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
Zend/zend_API.c (1)

1819-1846: ⚠️ Potential issue

Review implementation of scope-based class instantiation checks

The implementation adds proper scope checking for class instantiation, allowing nested classes to have visibility modifiers. The approach correctly handles both absolute scope requirements (for private classes) and inheritance-based scope requirements (for protected classes).

However, there's an issue with the protected class error handling:

Apply this diff to mirror the private-class pattern at line 1838:

-            zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
+            zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
+            ZVAL_NULL(arg);
+            Z_OBJ_P(arg) = NULL;
+            return FAILURE;
🧹 Nitpick comments (1)
Zend/zend_API.c (1)

1842-1846: Ensure lexical scope traversal is well-bounded

The lexical scope traversal looks like it might traverse up the chain if preloading has changed the class type. Consider documenting the maximum expected depth or adding an iteration limit to avoid potential infinite loops in pathological cases.

Consider adding a maximum iteration limit:

+    int max_depth = 10; // Prevent infinite loops
     // preloading may have changed the class type from ZEND_NAMESPACE_CLASS, so we need to check for user/internal class
-    if (check_class != scope && check_class->lexical_scope && (check_class->lexical_scope->type != ZEND_USER_CLASS && check_class->lexical_scope->type != ZEND_INTERNAL_CLASS)) {
+    if (check_class != scope && check_class->lexical_scope && (check_class->lexical_scope->type != ZEND_USER_CLASS && check_class->lexical_scope->type != ZEND_INTERNAL_CLASS) && max_depth-- > 0) {
         check_class = check_class->lexical_scope;
         goto check_lexical_scope;
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d02601f and c6f15c6.

📒 Files selected for processing (1)
  • Zend/zend_API.c (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: BENCHMARKING
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS

The idea here is to create something that can be used for other things
essentially making this quite flexible.

required scope: when defined, the class can only be used in the
                required scope, or its descendents when absolute is false
lexical scope: the scope the class is defined in -- either a namespace
               or another class.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (7)
tests/classes/inner_classes/enum_002.phpt (3)

20-27: Consider adding a default case to the match expression.

While the match expression handles all currently defined combinations, adding a default case would make the code more robust against future changes.

 return match ([$this->status, $this->processing]) {
     [Status::Active, Processing::Pending] => "Active and Pending",
     [Status::Active, Processing::Completed] => "Active and Completed",
     [Status::Inactive, Processing::Pending] => "Inactive and Pending",
     [Status::Inactive, Processing::Completed] => throw new LogicException('should not happen'),
+    default => throw new LogicException('Unexpected combination'),
 };

25-25: Test the exception case.

The exception case for [Status::Inactive, Processing::Completed] is defined but not actually tested. Consider adding a test to verify this exception is thrown as expected.

// Add to the test:
try {
    $message = Status::Inactive->createMessage();
    $message->processing = Processing::Completed; // This would need a setter or reflection
    $message->getMessage();
    echo "Test failed: Exception not thrown\n";
} catch (LogicException $e) {
    echo "Exception correctly thrown: " . $e->getMessage() . "\n";
}

1-46: Additional test recommendations.

Consider adding tests to verify:

  1. Visibility restrictions are enforced (attempt to access private enum from outside)
  2. Nested type reflection capabilities
  3. Integration with traits and interfaces

These would complement the current test cases and provide more comprehensive coverage.

tests/classes/inner_classes/interface_usage_003.phpt (1)

1-3: Test description could be more specific

The test correctly verifies that private inner classes aren't allowed in interfaces, but the test description "usage in an interface" is too generic. Consider making it more specific, such as "private inner class in an interface" to clearly differentiate it from other interface inner class tests.

--TEST--
-usage in an interface
+private inner class in an interface
--FILE--
tests/classes/inner_classes/interface_usage_001.phpt (1)

1-3: Test description could be more specific

The test correctly verifies public inner classes in interfaces, but the test description "usage in an interface" is too generic. Consider making it more specific, such as "public inner class in an interface" to clearly differentiate it from other interface inner class tests.

--TEST--
-usage in an interface
+public inner class in an interface
--FILE--
Zend/zend_compile.c (1)

9174-9209: Constructing fully qualified names for nested classes.

Building the nested class name by concatenating the active class’s name and the unqualified name ensures uniqueness. Using required_scope for private/protected classes is a neat way to enforce visibility. Consider adding tests to confirm that private nested classes remain inaccessible outside their parent scope.

Zend/zend_execute.c (1)

1050-1051: Consider adding bounds checking for recursion with goto statement.

The goto check_lexical_scope pattern creates recursion through lexical scopes. While this works correctly in most cases, it could theoretically lead to an infinite loop if there's a complex hierarchy or circular reference. Consider implementing a bounded loop with a maximum depth check as a safeguard.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd8e14 and 677138c.

📒 Files selected for processing (7)
  • Zend/zend_compile.c (17 hunks)
  • Zend/zend_execute.c (1 hunks)
  • tests/classes/inner_classes/enum_002.phpt (1 hunks)
  • tests/classes/inner_classes/interface_usage_001.phpt (1 hunks)
  • tests/classes/inner_classes/interface_usage_002.phpt (1 hunks)
  • tests/classes/inner_classes/interface_usage_003.phpt (1 hunks)
  • tests/classes/inner_classes/trait_usage_001.phpt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/classes/inner_classes/trait_usage_001.phpt
🧰 Additional context used
🧬 Code Definitions (2)
Zend/zend_execute.c (3)
Zend/zend_compile.c (8)
  • bool (309-323)
  • bool (528-531)
  • bool (2042-2052)
  • bool (2055-2066)
  • bool (2429-2454)
  • bool (9857-9877)
  • bool (9879-9941)
  • bool (9956-9967)
Zend/zend_object_handlers.c (3)
  • bool (259-270)
  • bool (971-976)
  • bool (1591-1615)
Zend/zend_execute.h (1)
  • zend_verify_property_type_error (528-528)
Zend/zend_compile.c (2)
Zend/zend_namespaces.h (2)
  • name (25-25)
  • name (26-26)
Zend/zend_namespaces.c (1)
  • zend_resolve_namespace (67-90)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: BENCHMARKING
🔇 Additional comments (29)
tests/classes/inner_classes/enum_002.phpt (8)

1-4: LGTM - Test declaration follows PHPT format correctly.

The test declaration follows the standard PHPT format with appropriate test name and section markers.


5-8: Appropriate enum definition with cases.

The Status enum is correctly defined with two cases, demonstrating the base enum functionality that will contain nested types.


9-12: Private nested enum defined correctly.

The private nested enum Processing demonstrates the core feature being tested - nested type declarations within an enum. The visibility modifier and case definitions look correct.


14-18: Nested class with constructor looks good.

The public nested class Message demonstrates another important aspect of nested types - a class inside an enum. The constructor with promoted property correctly accepts the parent enum type.


16-16: Correctly accessing private nested enum from containing context.

The initialization of the private property $processing demonstrates that nested types can access other nested types within the same context.


30-32: Factory method demonstrates proper instantiation of nested class.

The createMessage() method correctly demonstrates how to instantiate a nested class from within its containing enum.


35-36: Test cases cover key functionality.

The test code validates both object creation and method invocation, demonstrating the proper functioning of nested types.


38-45: Expected output confirms correct namespace handling.

The expected output confirms that namespaces are handled correctly, with the nested types appearing as Status\Message and Status\Processing in the var_dump output.

tests/classes/inner_classes/interface_usage_002.phpt (1)

1-13: LGTM: Test correctly verifies protected inner classes are not allowed in interfaces

This test validates that declaring a protected inner class within an interface correctly results in a fatal error, which is consistent with interface principles in PHP where all members should be public.

tests/classes/inner_classes/interface_usage_003.phpt (1)

4-13: LGTM: Test correctly verifies private inner classes are not allowed in interfaces

This test appropriately validates that declaring a private inner class within an interface results in a fatal error, which is consistent with interface principles in PHP where all members should be public.

tests/classes/inner_classes/interface_usage_001.phpt (1)

6-16: LGTM: Test properly validates inner class behavior in interfaces

This test effectively demonstrates several key aspects of inner classes in interfaces:

  1. Public inner classes are allowed in interfaces
  2. The inner class can be instantiated with the proper namespace path
  3. The inner class is not automatically included in implementations of the interface

The test provides good coverage of the expected behavior for public inner classes.

Zend/zend_compile.c (16)

41-41: Include header for namespace resolution.

Adding #include "zend_namespaces.h" here looks correct. It ensures that any symbols and functions for handling namespace logic (e.g. zend_resolve_namespace) are properly referenced without breaking compilation.


428-428: Initialize nested class queue pointer.

Setting CG(nested_class_queue) = NULL; is a good initialization step, avoiding undefined behavior. Ensure the queue is only allocated when necessary and properly freed at the end of compilation.


898-912: Allowing modifiers on nested classes.

Expanding valid modifiers (T_READONLY, T_ABSTRACT, T_FINAL) to include ZEND_MODIFIER_TARGET_NESTED_CLASS is consistent with the goal of supporting nested classes. The added || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK for T_FINAL also appears intentional, though it's worth verifying that property hooks and nested classes can indeed share these modifiers without unintended side effects.

Would you like to confirm whether property hook modifiers could conflict with nested class modifiers anywhere else in the codebase?


949-950: Proper string for “nested class” in error messages.

Defining "nested class" for the new target in error or debug messages helps maintain clarity and consistency when reporting invalid modifiers or usage.


1058-1088: Restrict certain access and static modifiers on nested classes.

Disallowing multiple access modifiers, static, or explicit visibility changes for nested classes is reasonable to keep them simpler and more predictable. Throwing a compile error when such modifiers are encountered prevents ambiguous or insecure behavior.


1267-1272: Use nested scope resolution if an active class entry is set.

This attempt to resolve the class name within the active class’s scope is a good fallback for nested class references. It aligns with the specialized lookup introduced in zend_resolve_class_in_scope.


9052-9052: New function signature for zend_compile_implements.

Declaring static void zend_compile_implements(zend_ast *ast, zend_class_entry *ce) in this file is appropriate for implementing interface support here. Confirm that the rest of the function code properly enforces interface declarations for nested or top-level classes.


9112-9146: Deferring and scanning nested class declarations.

zend_defer_class_decl() and zend_scan_nested_class_decl() demonstrate a clear workflow for accumulating AST nodes in CG(nested_class_queue) and then scanning them for nested classes. This deferred approach keeps top-level parsing cleaner and processes nested classes in a controlled manner.


9229-9230: Assigning scope for anonymous classes.

Setting ce->required_scope = NULL for anonymous classes is logical, and using either the current active class entry or the current namespace as ce->lexical_scope is consistent with the usual PHP behavior for anonymous classes.


9272-9274: Compiling implements list for nested classes.

Calling zend_compile_implements if implements_ast is present mirrors existing logic for normal classes. It ensures that nested classes properly handle all declared interfaces.


9290-9292: Scanning child AST of a namespace class.

zend_scan_nested_class_decl is triggered when ce->lexical_scope->type == ZEND_NAMESPACE_CLASS, ensuring that nested class declarations get processed. This keeps the logic consistent with normal namespaces while allowing deeper nesting.


9322-9322: Goto usage.

Jumping to compile_nested_classes label is a style choice the Zend Engine frequently uses for handling stateful compilation flows. This is acceptable if consistent with existing patterns.


9331-9331: Branching to compile nested classes again.

Similarly, this second goto compile_nested_classes; is consistent with the existing label-based flow. Maintaining the same approach helps unify the logic in the function.


9401-9419: Compiling queued nested classes after finishing the current class.

This block finalizes the current class, then processes each item in CG(nested_class_queue), restoring active_class_entry afterward. The queue destruction step prevents memory leaks. Good job ensuring the queue is re-initialized if needed in subsequent passes.


11709-11712: Deferring class declarations within an active class.

If there’s already an active_class_entry, deferring a new class’s compilation is a sensible approach. This preserves the correct lexical scope for the nested declaration.


1183-1207:

❓ Verification inconclusive

Resolve class names within nested scopes.

zend_resolve_class_in_scope() properly attempts to locate a class name by walking up the lexical_scope chain until reaching a namespace boundary or finding a match. The approach appears logical. However, consider verifying that no collisions occur if different hierarchies declare the same nested class name.


🏁 Script executed:

#!/bin/bash
# Searching for nested class resolution tests and potential name collisions
fd test --extension=phpt --extension=php | xargs grep -n -A 10 'nested class'

Length of output: 79


Attention: Manual Verification Needed for Nested Class Collision Handling

The logic in zend_resolve_class_in_scope() appears correct as it walks the lexical_scope chain until reaching a namespace boundary or finding a match. However, the absence of automated test results for scenarios involving potential name collisions (i.e., when different hierarchies declare the same nested class name) suggests that this case hasn’t been automatically validated. Please manually verify (or add relevant tests) to ensure that no collisions occur in such scenarios.

Zend/zend_execute.c (2)

1048-1084: New function zend_check_type_visibility for enforcing class visibility in property types.

This function checks whether a class type can be used with a property based on visibility rules:

  • Public classes can be used anywhere
  • Protected classes can only be used in protected/private properties where the property's class is a subclass of the class's required scope
  • Private classes can only be used in private properties where the property's class is the same as the class's required scope

The implementation correctly enforces visibility constraints according to PHP's nested class visibility design.


1088-1091: Incorrect evaluation of return codes in zend_check_type_visibility.

Because zend_check_type_visibility() returns SUCCESS (0) on success and FAILURE (1) on error, the condition will be true if the function returns failure, not success. For clarity and correctness, consider making the check explicit:

- if (Z_TYPE_P(property) == IS_OBJECT && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) {
+ if (Z_TYPE_P(property) == IS_OBJECT 
+     && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags) == FAILURE) {

This prevents confusion and ensures the code triggers on an actual failure rather than success.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
Zend/zend_API.c (1)

1838-1839: ⚠️ Potential issue

Potential missing return after throwing error.

Unlike other error blocks in this scope that nullify the object and return immediately upon raising a zend_type_error, lines 1838-1839 emit the error but do not perform the same cleanup or return. This could lead to inconsistent execution flow or side-effects if the function continues after the exception is thrown.

--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -1838,6 +1838,10 @@
   zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name));
 }
+  ZVAL_NULL(arg);
+  Z_OBJ_P(arg) = NULL;
+  return FAILURE;
🧹 Nitpick comments (12)
tests/classes/inner_classes/interface_usage_002.phpt (1)

1-2: Test description could be more specific.

The test name "usage in an interface" is quite generic. Consider using a more descriptive name that indicates what aspect of interface usage is being tested, such as "protected inner classes not allowed in interfaces".

tests/classes/inner_classes/interface_usage_003.phpt (1)

1-2: Test description could be more specific.

The test name "usage in an interface" is quite generic. Consider using a more descriptive name that indicates what aspect of interface usage is being tested, such as "private inner classes not allowed in interfaces".

ext/opcache/zend_persist.c (1)

1124-1143: LGTM: Well-implemented scope update function for nested classes.

This function properly handles the update of scope references for class entries during persistence, which is essential for supporting nested classes in opcache. It follows the established patterns in the rest of the file by:

  1. Checking if the scope reference exists
  2. Looking up the new memory location using the translation table
  3. Updating the reference if a valid entry is found

The code is clean and straightforward, handling both required_scope and lexical_scope in the same manner.

Consider adding a brief comment before the function explaining its purpose and when it should be called, as is done for other functions in this file.

tests/classes/inner_classes/interface_usage_001.phpt (2)

1-2: Test description could be more specific.

The test name "usage in an interface" is quite generic. Consider using a more descriptive name that indicates what aspect of interface usage is being tested, such as "public inner classes in interfaces - basic usage and scoping".


10-15: LGTM: Thoroughly tests namespace and inheritance behavior.

This test effectively verifies two key aspects of inner classes in interfaces:

  1. The inner class is accessible through the interface's namespace (Outer\Inner)
  2. The inner class is not automatically inherited by implementing classes (Foo\Inner doesn't exist)

This covers the fundamental behavior of inner classes with interfaces.

Consider adding a test case that explicitly extends Outer\Inner to verify that inheritance works correctly for inner classes defined in interfaces.

Zend/zend_API.c (1)

1842-1846: Consider reducing “goto” usage.

The jump at line 1845 is valid, but replacing the label-based flow with a clear loop or condition block can improve readability and maintainability.

Zend/zend_vm_execute.h (2)

10807-10822: Consider refactoring duplicated code into a helper function

The same visibility checking code appears in 5 different locations with identical logic. This creates maintenance challenges if the rules need to be modified in the future.

// Add a new helper function elsewhere in the file
+ static zend_always_inline void check_return_object_visibility(zval *retval_ptr) {
+   if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) {
+     if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) {
+       if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) {
+         zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));
+         HANDLE_EXCEPTION();
+       } else {
+         zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));
+         HANDLE_EXCEPTION();
+       }
+     } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) {
+       if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) {
+         zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));
+         HANDLE_EXCEPTION();
+       }
+     }
+   }
+ }

// Then in each location, replace the block with:
+ check_return_object_visibility(retval_ptr);

Also applies to: 21562-21577, 30060-30075, 37872-37887, 50691-50706


10810-10811: Consider enhancing error messages with context about nested classes

The error messages correctly identify the visibility mismatch but don't provide context about nested classes. This might be confusing for developers unfamiliar with nested class concepts.

- zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));
+ zend_type_error("Public method %s cannot return private nested class %s - private nested classes are only accessible within their containing class", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name));

Also applies to: 21565-21566, 30063-30064, 37875-37876, 50694-50695

Zend/zend_compile.c (1)

1058-1088: Prohibit visibility modifiers on nested classes
These checks ensure that public/protected/private or static modifiers are disallowed for nested classes at compile time. If this restriction is intentional, the clear error messages are helpful. However, confirm that design constraints truly require these classes to be inaccessible at all visibility levels.

Do you want to confirm whether truly no readability or visibility is desired for nested classes? If needed, I can assist with a script to gather usage references or open a follow-up discussion.

tests/classes/inner_classes/enum_002.phpt (3)

14-18: Consider using enum keyword instead of class keyword inside enum.

While PHP allows using the class keyword here, for clarity and consistency with modern PHP practices, you might consider using the enum keyword to define inner enums and the class keyword only for inner classes.


20-27: Consider adding safeguards against invalid enum states.

The getMessage() method throws an exception for the [Status::Inactive, Processing::Completed] combination, but there are no safeguards to prevent this state from occurring. Consider adding validation in the constructor or setter methods to prevent this invalid combination.

public function __construct(private Status $status) {
+    // Prevent invalid combinations that would throw exceptions later
+    if ($status === Status::Inactive && $this->processing === Processing::Completed) {
+        throw new LogicException('Invalid state: Inactive status cannot have Completed processing');
+    }
}

1-46: Enhance test coverage for completeness.

Consider adding test cases for all possible enum combinations to ensure complete coverage. Currently, the test only demonstrates Status::Active->createMessage() and Status::Inactive->createMessage()->getMessage() but does not test the behavior with Processing::Completed.

// Add tests for other combinations
$activeMessage = Status::Active->createMessage();
// Access to set private property would require reflection in a real test
$activeMessage->setProcessingForTest(Processing::Completed);
echo $activeMessage->getMessage() . "\n"; // Should output "Active and Completed"

// Test exception case
try {
    $inactiveMessage = Status::Inactive->createMessage();
    $inactiveMessage->setProcessingForTest(Processing::Completed);
    $inactiveMessage->getMessage(); // Should throw LogicException
    echo "Failed: Exception not thrown\n";
} catch (LogicException $e) {
    echo "Caught expected exception: " . $e->getMessage() . "\n";
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd8e14 and 21f7dfc.

📒 Files selected for processing (74)
  • Zend/Optimizer/dfa_pass.c (1 hunks)
  • Zend/tests/errmsg/errmsg_027.phpt (1 hunks)
  • Zend/zend.h (1 hunks)
  • Zend/zend_API.c (1 hunks)
  • Zend/zend_compile.c (17 hunks)
  • Zend/zend_compile.h (2 hunks)
  • Zend/zend_execute.c (1 hunks)
  • Zend/zend_execute_API.c (3 hunks)
  • Zend/zend_globals.h (2 hunks)
  • Zend/zend_language_parser.y (4 hunks)
  • Zend/zend_namespaces.c (1 hunks)
  • Zend/zend_namespaces.h (1 hunks)
  • Zend/zend_object_handlers.c (7 hunks)
  • Zend/zend_opcode.c (2 hunks)
  • Zend/zend_vm_def.h (1 hunks)
  • Zend/zend_vm_execute.h (5 hunks)
  • configure.ac (1 hunks)
  • ext/opcache/zend_persist.c (2 hunks)
  • ext/reflection/php_reflection.c (1 hunks)
  • ext/reflection/php_reflection.stub.php (1 hunks)
  • ext/reflection/php_reflection_arginfo.h (4 hunks)
  • ext/reflection/tests/ReflectionClass_toString_001.phpt (2 hunks)
  • ext/reflection/tests/bug74454.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_001.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_002.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_003.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_004.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_005.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_006.phpt (1 hunks)
  • tests/classes/inner_classes/access_modifiers_007.phpt (1 hunks)
  • tests/classes/inner_classes/aliases.phpt (1 hunks)
  • tests/classes/inner_classes/autoload_001.phpt (1 hunks)
  • tests/classes/inner_classes/autoload_002.phpt (1 hunks)
  • tests/classes/inner_classes/enum_001.phpt (1 hunks)
  • tests/classes/inner_classes/enum_002.phpt (1 hunks)
  • tests/classes/inner_classes/enum_usage.phpt (1 hunks)
  • tests/classes/inner_classes/errors_001.phpt (1 hunks)
  • tests/classes/inner_classes/errors_002.phpt (1 hunks)
  • tests/classes/inner_classes/inheritance.phpt (1 hunks)
  • tests/classes/inner_classes/inner_classes.inc (1 hunks)
  • tests/classes/inner_classes/instantiation_001.phpt (1 hunks)
  • tests/classes/inner_classes/interface_usage_001.phpt (1 hunks)
  • tests/classes/inner_classes/interface_usage_002.phpt (1 hunks)
  • tests/classes/inner_classes/interface_usage_003.phpt (1 hunks)
  • tests/classes/inner_classes/readonly.phpt (1 hunks)
  • tests/classes/inner_classes/reflection_001.phpt (1 hunks)
  • tests/classes/inner_classes/resolution_001.phpt (1 hunks)
  • tests/classes/inner_classes/resolution_002.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_001.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_002.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_003.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_004.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_005.phpt (1 hunks)
  • tests/classes/inner_classes/return_types_006.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_001.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_002.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_003.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_004.phpt (1 hunks)
  • tests/classes/inner_classes/simple_declaration_005.phpt (1 hunks)
  • tests/classes/inner_classes/static_variables.phpt (1 hunks)
  • tests/classes/inner_classes/trait_usage_001.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_001.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_002.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_003.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_004.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_005.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_006.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_007.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_008.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_009.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_010.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_011.phpt (1 hunks)
  • tests/classes/inner_classes/visibility_012.phpt (1 hunks)
  • win32/build/config.w32 (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (60)
  • tests/classes/inner_classes/errors_002.phpt
  • ext/reflection/tests/bug74454.phpt
  • Zend/tests/errmsg/errmsg_027.phpt
  • tests/classes/inner_classes/resolution_001.phpt
  • tests/classes/inner_classes/access_modifiers_003.phpt
  • tests/classes/inner_classes/errors_001.phpt
  • tests/classes/inner_classes/enum_usage.phpt
  • tests/classes/inner_classes/access_modifiers_001.phpt
  • tests/classes/inner_classes/access_modifiers_006.phpt
  • tests/classes/inner_classes/inner_classes.inc
  • tests/classes/inner_classes/access_modifiers_004.phpt
  • tests/classes/inner_classes/simple_declaration_002.phpt
  • tests/classes/inner_classes/access_modifiers_005.phpt
  • tests/classes/inner_classes/simple_declaration_001.phpt
  • tests/classes/inner_classes/aliases.phpt
  • tests/classes/inner_classes/autoload_002.phpt
  • tests/classes/inner_classes/readonly.phpt
  • Zend/zend_opcode.c
  • configure.ac
  • tests/classes/inner_classes/access_modifiers_002.phpt
  • tests/classes/inner_classes/autoload_001.phpt
  • tests/classes/inner_classes/return_types_001.phpt
  • Zend/zend_compile.h
  • tests/classes/inner_classes/return_types_003.phpt
  • ext/reflection/php_reflection.stub.php
  • tests/classes/inner_classes/access_modifiers_007.phpt
  • Zend/zend_execute_API.c
  • tests/classes/inner_classes/inheritance.phpt
  • tests/classes/inner_classes/instantiation_001.phpt
  • tests/classes/inner_classes/return_types_002.phpt
  • tests/classes/inner_classes/enum_001.phpt
  • Zend/Optimizer/dfa_pass.c
  • tests/classes/inner_classes/return_types_006.phpt
  • tests/classes/inner_classes/visibility_006.phpt
  • tests/classes/inner_classes/simple_declaration_003.phpt
  • Zend/zend.h
  • ext/reflection/tests/ReflectionClass_toString_001.phpt
  • tests/classes/inner_classes/visibility_001.phpt
  • Zend/zend_globals.h
  • tests/classes/inner_classes/visibility_012.phpt
  • tests/classes/inner_classes/visibility_004.phpt
  • tests/classes/inner_classes/return_types_005.phpt
  • tests/classes/inner_classes/trait_usage_001.phpt
  • tests/classes/inner_classes/reflection_001.phpt
  • tests/classes/inner_classes/visibility_010.phpt
  • tests/classes/inner_classes/static_variables.phpt
  • tests/classes/inner_classes/simple_declaration_004.phpt
  • tests/classes/inner_classes/simple_declaration_005.phpt
  • tests/classes/inner_classes/visibility_008.phpt
  • tests/classes/inner_classes/visibility_007.phpt
  • tests/classes/inner_classes/return_types_004.phpt
  • tests/classes/inner_classes/visibility_005.phpt
  • tests/classes/inner_classes/resolution_002.phpt
  • tests/classes/inner_classes/visibility_009.phpt
  • ext/reflection/php_reflection_arginfo.h
  • win32/build/config.w32
  • Zend/zend_object_handlers.c
  • tests/classes/inner_classes/visibility_003.phpt
  • Zend/zend_namespaces.h
  • tests/classes/inner_classes/visibility_011.phpt
🧰 Additional context used
🧬 Code Definitions (4)
Zend/zend_namespaces.c (1)
Zend/zend_namespaces.h (3)
  • name (25-25)
  • name (26-26)
  • zend_destroy_namespaces (27-27)
Zend/zend_compile.c (2)
Zend/zend_namespaces.h (2)
  • name (25-25)
  • name (26-26)
Zend/zend_namespaces.c (1)
  • zend_resolve_namespace (67-90)
ext/reflection/php_reflection.c (1)
ext/reflection/php_reflection.stub.php (14)
  • ReflectionClass (233-443)
  • isPrivate (191-191)
  • isPrivate (438-438)
  • isPrivate (516-516)
  • isPrivate (612-612)
  • isProtected (194-194)
  • isProtected (442-442)
  • isProtected (519-519)
  • isProtected (615-615)
  • isPublic (188-188)
  • isPublic (440-440)
  • isPublic (513-513)
  • isPublic (609-609)
  • isInnerClass (436-436)
Zend/zend_execute.c (2)
Zend/zend_API.c (9)
  • bool (497-524)
  • bool (3885-4132)
  • bool (4202-4310)
  • bool (4313-4326)
  • bool (4328-4331)
  • bool (4334-4349)
  • bool (4542-4545)
  • bool (5315-5325)
  • bool (5328-5342)
Zend/zend_compile.c (7)
  • bool (309-323)
  • bool (528-531)
  • bool (2042-2052)
  • bool (2055-2066)
  • bool (2429-2454)
  • bool (9859-9879)
  • bool (9881-9943)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: LINUX_X64_DEBUG_ZTS_ASAN
  • GitHub Check: WINDOWS_X64_ZTS
  • GitHub Check: LINUX_X64_RELEASE_NTS
  • GitHub Check: FREEBSD
  • GitHub Check: BENCHMARKING
  • GitHub Check: LINUX_X32_DEBUG_ZTS
  • GitHub Check: MACOS_ARM64_DEBUG_NTS
🔇 Additional comments (59)
tests/classes/inner_classes/interface_usage_002.phpt (2)

6-8: LGTM: Correctly testing interface visibility constraints.

This test correctly verifies that PHP doesn't allow protected inner classes in interfaces, which is consistent with PHP's design where interfaces can only contain public members.


11-12: LGTM: Expected error message is appropriate.

The expected error message clearly indicates why the code is invalid, helping developers understand the constraints of inner classes in interfaces.

tests/classes/inner_classes/interface_usage_003.phpt (2)

6-8: LGTM: Correctly testing interface visibility constraints.

This test correctly verifies that PHP doesn't allow private inner classes in interfaces, which is consistent with PHP's design where interfaces can only contain public members.


11-12: LGTM: Expected error message is appropriate.

The expected error message clearly indicates why the code is invalid, helping developers understand the constraints of inner classes in interfaces.

ext/opcache/zend_persist.c (1)

1318-1318: LGTM: Proper integration with the class persistence process.

The call to zend_update_ce_scopes is correctly placed after updating parent classes, ensuring that all class entry scope references are properly updated during the persistence process.

tests/classes/inner_classes/interface_usage_001.phpt (2)

6-8: LGTM: Good test of public inner class in interface.

This correctly tests that public inner classes are allowed within interfaces, in contrast to the other tests that verify protected and private classes are not allowed.


17-21: LGTM: Expected output confirms correct behavior.

The expected output correctly shows:

  1. An instance of Outer\Inner can be created
  2. Outer\Inner exists as a class
  3. Foo\Inner does not exist, confirming inner classes aren't inherited

This validates the expected behavior of inner classes in interfaces.

Zend/zend_vm_def.h (2)

4431-4439: Extract repeated type error logic into a helper function.

This closely mirrors a past review comment suggesting the same refactor. The checks for returning private/protected classes from public methods repeat similar patterns and could be centralized in a dedicated helper for maintainability.


4440-4445:

✅ Verification successful

Ensure thorough test coverage for protected methods returning private classes.

The logic here looks consistent with the checks above. However, to prevent regressions, please confirm that guarded return types are properly exercised in your test suite.

Below is a sample script you can use to locate test cases covering “cannot return private class” or “protected method” checks. It searches for relevant phrases in test files:


🏁 Script executed:

#!/bin/bash
rg -A 3 -i 'cannot return private class|protected method' tests/

Length of output: 2354


Test Coverage for Guarded Return Types Verified

The code in Zend/zend_vm_def.h (lines 4440–4445) correctly enforces that protected methods cannot return objects from a private class. I verified that the test suite already covers related scenarios (e.g., in files such as tests/classes/inner_classes/return_types_004.phpt, tests/classes/protected_001b.phpt, and tests/classes/protected_002.phpt). Please double-check these tests to ensure they comprehensively exercise all edge cases for guarded return types and prevent any regressions.

tests/classes/inner_classes/visibility_002.phpt (7)

1-3: LGTM - Clear test description

The test name and description clearly indicate that this test verifies the ability to access outer class private variables from an inner class.


6-12: Well-structured nested class implementation with appropriate visibility

The test correctly defines an Outer class with a private inner class Inner. The inner class has a method that accesses a private property of the outer class, which is a key feature of inner classes that this test verifies.


8-10: Verify inner class access to outer class private members

This test properly demonstrates that an inner class can access private members of its outer class, which is an important behavior to verify for nested classes implementation.


12-12: Proper type declaration with inner class

The property declaration correctly uses the inner class type for the private property, demonstrating type declarations with inner classes work as expected.


14-16: Correct instantiation of inner class from outer class method

The test method properly demonstrates how to instantiate an inner class from within an outer class method, which is a fundamental aspect of the nested classes feature.


19-22: Appropriate test execution flow

The test has a logical flow: creating an outer class instance, invoking its method (which uses the inner class), and then verifying the results with var_dump.


25-30: Expected output matches the nested class behavior

The expected output correctly verifies:

  1. The private property is accessible and modified by the inner class
  2. The namespace resolution of the inner class (Outer\Inner)
  3. The object structure with proper visibility modifiers

This demonstrates the inner class works as intended with regard to accessing private properties of the outer class.

Zend/zend_vm_execute.h (2)

10807-10822: Sound implementation of visibility checks for nested classes

The code correctly enforces visibility restrictions for methods returning nested class instances:

  • Prevents public methods from returning private classes
  • Prevents public methods from returning protected classes
  • Prevents protected methods from returning unrelated private classes

This enforces proper encapsulation principles for nested classes.


10815-10822:

❓ Verification inconclusive

Consider adding visibility checks for private methods

The current implementation checks visibility for public and protected methods, but doesn't appear to have specific checks for private methods returning nested classes. Is this intentional, or should there be similar restrictions for private methods?

Also applies to: 21570-21577, 30068-30075, 37880-37887, 50699-50706


🏁 Script executed:

#!/bin/bash
# Find any tests related to private methods returning nested classes
rg -i "private method.*return.*private class|private method.*return.*protected class" --type php

Length of output: 98


Visibility Checks for Private Methods – Manual Verification Recommended

It appears that the current implementation in Zend/zend_vm_execute.h (lines 10815-10822) only performs visibility checks for public and protected methods. The concern is whether similar restrictions should also apply to private methods when they return nested classes. Additionally, the related locations (21570-21577, 30068-30075, 37880-37887, 50699-50706) might need review regarding this behavior.

A search for tests using a regex for patterns like "private method.*return.*private class|private method.*return.*protected class" did not yield any results. This outcome is inconclusive, so please manually verify whether there are any tests covering private methods returning nested classes elsewhere in the codebase. If such tests are absent—and if enforcing a similar visibility check for private methods would be beneficial—additional restrictions or tests may be needed.

ext/reflection/php_reflection.c (4)

4142-4150: Correctly implements the ReflectionClass::isPrivate() method.

This implementation properly checks if a class is private by evaluating both the required_scope and required_scope_absolute properties. The logic accurately returns true only when both conditions are met.


4153-4163: Correctly implements the ReflectionClass::isProtected() method.

This implementation correctly identifies protected classes by checking if they have a required scope but without an absolute scope requirement. The method returns true if required_scope is set and required_scope_absolute is false.


4166-4174: Correctly implements the ReflectionClass::isPublic() method.

The method correctly implements the public access check by verifying that the class doesn't have a required scope. This is consistent with the visibility model for nested classes.


4177-4188: Correctly implements the ReflectionClass::isInnerClass() method.

This method properly determines if a class is an inner class by checking if it has a lexical scope that's not a namespace class. The implementation aligns with the nested class model introduced in this PR.

Zend/zend_compile.c (16)

41-41: Include header for namespace resolutions
This addition of #include "zend_namespaces.h" seems necessary for nested class scoping and looks consistent with the surrounding code.


428-428: Initialize the nested class queue
Setting CG(nested_class_queue) = NULL; is a clear approach to prepare for deferred nested-class compilation.


898-904: Enable modifiers for nested classes
Allowing readonly, abstract, and final on nested classes is consistent with the new ZEND_MODIFIER_TARGET_NESTED_CLASS target. The logic integrates well with existing cases.

Also applies to: 911-912


949-950: Add descriptive label for nested classes
Defining the modifier target name as "nested class" helps produce accurate error messages and maintain clarity in code diagnostics.


1183-1207: Check for potential cyclic scopes
Iterating up the lexical_scope chain is fine as long as circular references cannot form. Ensure no recursion can occur if a class mistakenly points back to itself.

Would you like a script to scan for any place in the codebase that might set ce->lexical_scope = ce, which could lead to an infinite loop?


1267-1272: Return nested class name from active scope
Resolving a nested class name from an active class entry is straightforward and matches the general approach used elsewhere.


9054-9054: Introduce explicit signature for zend_compile_implements
No immediate concerns. This function’s signature addition aligns with the new logic for class-based checks.


9114-9150: Defer and scan nested class declarations
Deferring class declarations in zend_defer_class_decl and scanning them in zend_scan_nested_class_decl is a clean solution for multi-stage compilation.


9175-9212: Assemble nested class name and handle trait conflicts
Appending parent class names with “\” to form nested names, and checking for conflicts in traits, looks correct and consistent with how PHP builds these symbolic references.


9231-9232: Set up required and lexical scope for anonymous classes
Initializing ce->required_scope and ce->lexical_scope for anonymous classes helps maintain correct scoping, especially in nested scenarios.


9274-9276: Compile interfaces on nested classes
Ensuring nested classes can implement interfaces is essential for consistent OOP behavior.


9292-9294: Scan nested classes in namespace context
Conditionally scanning for inner classes under a namespace class entry is consistent with the concept of lexical scoping.


9324-9324: Jump to compile nested classes
Using goto compile_nested_classes; ensures that classes are compiled once main linking steps conclude.


9333-9333: Link class before compiling nested classes
Jumping to the label for nested class compilation preserves the correct linking order.


9403-9421: Compile nested classes from queue
This final step defers all nested class declarations, processing them once the outer class has been established. The logic is straightforward and maintains clarity around scoping.


11711-11713: Defer classes when an active class scope is found
Rejecting immediate parsing and delegating the nested class for later compilation matches the prior approach, ensuring consistent code structure.

tests/classes/inner_classes/enum_002.phpt (8)

1-3: Test naming and description are clear and appropriate.

The test name "nested enums" accurately reflects the purpose of this test case, which is to verify the functionality of nested enumerations in PHP.


5-8: Enum declaration looks good.

The Status enum is properly defined with two clear and semantically appropriate cases: Active and Inactive.


9-12: Private nested enum is correctly implemented.

The Processing enum is appropriately nested within Status and correctly marked as private, demonstrating the visibility control for nested types.


16-16: Property initialization looks good.

The private property $processing is correctly initialized to the default enum case Processing::Pending.


18-18: Constructor with property promotion is well implemented.

The constructor efficiently uses PHP 8.0's property promotion feature to declare and initialize the $status property.


30-32: Factory method is implemented correctly.

The createMessage() method provides a clean factory pattern for creating Message instances from the enum value.


35-36: Test usage demonstrates functionality well.

The test code demonstrates both object creation and method invocation patterns effectively, showing how to use the nested types from external code.


38-46: Expected output is comprehensive.

The expected output correctly verifies both the object structure (including proper namespace resolution for nested types) and the functional behavior of the getMessage() method.

Zend/zend_execute.c (2)

1048-1084: New function correctly implements visibility checking for nested classes

The new zend_check_type_visibility function handles class type visibility checks through several well-structured cases (public, protected, private) and includes appropriate recursive checks for different lexical scopes.


1088-1091: Potential mismatch of return codes in zend_check_type_visibility

Because zend_check_type_visibility() returns SUCCESS (0) on success and FAILURE (1) on error, the condition:

if (Z_TYPE_P(property) == IS_OBJECT && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) {
    ...
}

will be true if the function returns failure, not success. For clarity and correctness, consider making the check explicit, for example:

- if (Z_TYPE_P(property) == IS_OBJECT && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) {
+ if (Z_TYPE_P(property) == IS_OBJECT
+     && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags) == FAILURE) {

This prevents confusion and ensures the code triggers on an actual failure rather than success.

Zend/zend_language_parser.y (5)

288-289: Validate new grammar type usage.

Declaring nested_class_statement under %type <ast> optional_parameter_list ensures that the parser can handle nested class statements in conjunction with parameter lists. Please confirm that these additions do not inadvertently introduce grammar conflicts or shift/reduce errors elsewhere.


291-291: Addition of nested_class_modifiers looks solid.

Extending the %type <num> list to include nested_class_modifiers appears consistent with existing rules for other modifiers.


631-637: Confirm default nested class visibility.

By defaulting to ZEND_ACC_PUBLIC when modifiers are empty, this rule makes nested classes publicly visible. Please verify that this default aligns with intended language semantics, or consider requiring explicit visibility.


955-958: Check enum handling in nested_class_statement.

Allowing an enum_declaration_statement here ensures nested enums parse as well. If nested enums need specialized logic (e.g., custom attributes or flags), consider validating them separately or verifying they behave consistently with top-level enums.


978-978: Attach modifiers to nested class statement correctly.

Combining nested_class_modifiers and nested_class_statement is a good approach. Confirm the modifiers (e.g., final, abstract, readonly) propagate correctly to nested classes or enums.

Zend/zend_namespaces.c (6)

1-18: License and file header look fine.

The header reflects standard Zend licensing with the correct authors. No issues noted.


20-34: Accurate use of sizeof(zend_class_entry) in create_namespace.

Allocating the correct size for zend_class_entry and calling zend_initialize_class_data() resolves previous concerns about incorrect memory allocation.


36-65: Possible substring handling and loop condition issue.

As noted in previous reviews, reusing ZSTR_VAL(lc_name) for each segment might cause incorrect hash table entries if multiple namespace segments are processed. Also, while (pos <= end) can be error-prone if not carefully checked to avoid out-of-bounds access. This mirrors past concerns.


67-90: Global namespace initialization logic appears correct.

Creating EG(global_namespace) on the fly and updating EG(namespaces) consistently ensures namespace resolution. No immediate issues beyond concurrency, which is typically out of scope for the Zend Engine.


92-98: Null check recommended in zend_lookup_namespace.

A past refactoring suggestion indicated adding a null check for name (similar to zend_resolve_namespace). If calls to zend_lookup_namespace(NULL) might occur, this function could become more robust by returning EG(global_namespace) or NULL.


100-107: Namespace destruction logic is sound.

Hash table destruction frees all namespace entries, resetting EG(namespaces) and EG(global_namespace). Implementation looks correct.

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.

1 participant