Skip to content

Conversation

@Ronbb
Copy link
Owner

@Ronbb Ronbb commented Jul 24, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a new opc_comn crate with client and server modules, including base server functionality and traits for OPC Common and Shutdown interfaces.
    • Added comprehensive OPC Data Access (DA) server, group, item, browsing, asynchronous and synchronous I/O, and data callback interfaces with public structs and traits.
    • Enhanced memory management utilities with new macros for pointer and wide string handling, plus expanded support for pointer arrays and wide strings.
    • Added usage examples and documentation for new convenience macros in opc_classic_utils.
    • Added OPC server list creation and utility functions for COM class ID resolution.
  • Documentation

    • Expanded documentation for memory management macros and usage examples.
    • Added README files highlighting project status and directing users to documentation.
  • Bug Fixes

    • Improved pointer and memory management logic in utility crates, ensuring safer handling and clearer drop semantics.
  • Style

    • Updated string conversion methods in examples and tests to use lossy wide string conversions.
    • Modernized formatting in example print statements for improved readability.
  • Chores

    • Updated package metadata and workspace configuration.
    • Added .gitignore files to exclude build artifacts.

Ronbb added 4 commits July 18, 2025 16:25
- Update Cargo.toml workspace dependencies
- Clean up build.rs files in binding crates
- Update Cargo.lock with latest dependency versions
…lity

Key improvements:
1. Fix Drop implementation for Caller* types
   - CallerAllocatedArray: preserve pointer integrity, only clear length
   - CallerAllocatedPtr: maintain pointer validity
   - CallerAllocatedWString: keep pointer intact
   - Align with COM memory management conventions

2. Add convenience macros
   - write_caller_allocated_ptr!: write to caller-allocated pointers
   - write_caller_allocated_array!: write to caller-allocated arrays
   - alloc_callee_wstring!: allocate callee-allocated wide strings
   - All macros include comprehensive documentation and examples

3. Add opc_comn module
   - Implement OPC Common interface
   - Provide basic COM functionality support

4. Enhance documentation and examples
   - Update README.md with macro usage instructions
   - Add macro_usage.rs example file
   - Add detailed documentation for all macros

5. Code quality improvements
   - Pass all clippy checks
   - Pass all unit tests
   - Fix memory management issues

These improvements make OPC Classic API development safer and more convenient.
@coderabbitai
Copy link

coderabbitai bot commented Jul 24, 2025

Walkthrough

This change introduces a new crate, opc_comn, implementing OPC Common COM interfaces and server-side traits for OPC interoperability. It adds trait-based abstractions and COM interface implementations for IOPCCommon and IOPCShutdown, including all necessary modules, traits, and server base logic. The workspace configuration is updated, and supporting documentation and ignore files are included. The crate exposes public client and server modules with submodules for OPC server list functionality, utility functions, and server base implementations. No client-side or functional logic beyond module declarations and basic COM wrappers is implemented yet.

Changes

File(s) Change Summary
Cargo.toml Workspace updated to include new member crate opc_comn and its path dependency.
opc_comn/.gitignore
opc_comn/Cargo.toml
opc_comn/README.md
New crate opc_comn added with metadata, documentation, and ignore settings.
opc_comn/src/lib.rs Declares public client and server modules.
opc_comn/src/client/mod.rs Adds public modules opc_server_list and utils.
opc_comn/src/client/opc_server_list.rs Implements OpcServerList struct and create_opc_server_list function for OPC server list COM interface.
opc_comn/src/client/utils.rs Adds utility function to get class ID from programmatic ID.
opc_comn/src/server/mod.rs Declares public modules opc_server_base and traits.
opc_comn/src/server/opc_server_base.rs Implements OpcServerBase<T> struct with IOPCCommon and IOPCShutdown COM interface logic.
opc_comn/src/server/traits/mod.rs Declares submodules opc_common and opc_shutdown.
opc_comn/src/server/traits/opc_common.rs Defines OpcCommon trait for locale, error, and client info management.
opc_comn/src/server/traits/opc_shutdown.rs Defines OpcShutdown trait for shutdown requests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OpcServerBase
    participant ImplT as T: OpcCommon + OpcShutdown

    Client->>OpcServerBase: SetLocaleID(dwlcid)
    OpcServerBase->>ImplT: set_locale_id(dwlcid)
    ImplT-->>OpcServerBase: Result
    OpcServerBase-->>Client: Result

    Client->>OpcServerBase: GetLocaleID()
    OpcServerBase->>ImplT: get_locale_id()
    ImplT-->>OpcServerBase: Result<u32>
    OpcServerBase-->>Client: Result<u32>

    Client->>OpcServerBase: QueryAvailableLocaleIDs()
    OpcServerBase->>ImplT: query_available_locale_ids()
    ImplT-->>OpcServerBase: Result<Vec<u32>>
    OpcServerBase-->>Client: Result

    Client->>OpcServerBase: GetErrorString(dwerror)
    OpcServerBase->>ImplT: get_error_string(dwerror)
    ImplT-->>OpcServerBase: Result<String>
    OpcServerBase-->>Client: Result<PWSTR>

    Client->>OpcServerBase: SetClientName(szname)
    OpcServerBase->>ImplT: set_client_name(szname)
    ImplT-->>OpcServerBase: Result
    OpcServerBase-->>Client: Result

    Client->>OpcServerBase: ShutdownRequest(szreason)
    OpcServerBase->>ImplT: shutdown_request(szreason)
    ImplT-->>OpcServerBase: Result
    OpcServerBase-->>Client: Result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Ronbb/rust_opc#18: Updates OPC Common IDL files and bindings, which is directly related as this PR implements the Rust-side logic for those COM interfaces.
  • Ronbb/rust_opc#20: Splits and adds multiple OPC binding crates, including opc_comn_bindings, which is now used as a dependency in the new opc_comn crate, showing a direct connection in workspace and binding structure.

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2c758e1 and cd3d8a7.

📒 Files selected for processing (3)
  • opc_classic_utils/src/memory/tests.rs (3 hunks)
  • opc_classic_utils/src/memory/wstring.rs (10 hunks)
  • opc_comn/src/client/opc_server_list.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • opc_classic_utils/src/memory/tests.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
opc_classic_utils/src/memory/wstring.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

opc_comn/src/client/opc_server_list.rs (5)

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

🧬 Code Graph Analysis (1)
opc_comn/src/client/opc_server_list.rs (2)
opc_classic_utils/src/memory/wstring.rs (5)
  • std (52-52)
  • std (363-363)
  • null (231-235)
  • from_str (168-180)
  • from_str (357-371)
opc_comn/src/client/utils.rs (1)
  • get_class_id_from_program_id (1-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyzing
🔇 Additional comments (19)
opc_comn/src/client/opc_server_list.rs (7)

5-11: LGTM: Well-designed configuration struct.

The configuration struct provides appropriate optional parameters for COM object creation with sensible defaults and proper lifetime annotation for borrowed references.


13-15: LGTM: Clean wrapper struct design.

The struct appropriately wraps the COM interface with public access for flexibility.


17-68: LGTM: Robust COM object creation with proper error handling.

The method correctly handles both local and remote COM object creation scenarios with appropriate error checking and safe interface casting. The fallback to standard "OPC.ServerList.1" program ID is sensible.


70-73: LGTM: Appropriate data structure for COM string results.

The struct correctly uses optional strings to handle potentially null COM string outputs.


75-85: LGTM: Clean wrapper for COM enumeration method.

The method appropriately wraps the COM call with proper parameter types and return value handling.


87-106: LGTM: Proper handling of COM string output parameters.

The method correctly initializes null output parameters, handles callee-allocated strings, and provides appropriate error handling for string conversions.


108-118: LGTM: Correct string conversion and COM method call.

The method properly converts the Rust string to a caller-allocated wide string and handles the COM call with appropriate error handling.

opc_classic_utils/src/memory/wstring.rs (12)

41-46: LGTM: Consistent PWSTR support added.

The new from_pwstr method follows the same pattern as the existing from_pcwstr method, maintaining consistency in the API.


131-134: LGTM: Consistent PWSTR accessor method.

The as_pwstr method complements the existing as_pcwstr method, providing consistent access to both pointer types.


117-119: LGTM: Simplified into_raw method.

The method correctly returns the raw pointer without additional logic, which is appropriate for transferring ownership.


137-142: LGTM: Clarified Drop behavior.

The updated comments clearly explain that the pointer is kept intact for the callee to use, which aligns with caller-allocated memory semantics.


183-189: LGTM: Standard TryFrom trait implementation.

The implementation follows Rust conventions and provides a clean conversion path from PCWSTR with appropriate error handling.


224-235: LGTM: Useful convenience methods added.

The from_string method provides a nice convenience wrapper, and the null constructor is useful for initializing output parameters.


293-295: LGTM: Consistent into_raw method.

The method correctly returns the raw pointer, following the same pattern as CallerAllocatedWString.


307-320: LGTM: Essential methods for COM output parameters.

The mutable pointer methods (as_pcwstr_mut_ptr and as_pwstr_mut_ptr) are essential for COM methods that take output string parameters. The PWSTR accessor method maintains API consistency.


270-285: LGTM: Robust string conversion with proper UTF-16 validation.

The new to_string method provides better error handling than to_string_lossy by properly validating UTF-16 sequences and returning appropriate errors for malformed data.


374-430: LGTM: Well-designed macro with comprehensive documentation.

The macro provides a clean abstraction for callee-allocated wide string creation with excellent documentation and examples. However, it depends on the FromStr implementation which currently has a buffer overflow issue that needs to be fixed first.

The macro implementation is sound, but ensure the underlying FromStr implementation buffer overflow is resolved before using this macro in production.


77-77: LGTM: Consistent method naming.

The method name to_string_lossy clearly indicates the lossy conversion behavior.


242-252: LGTM: Improved length calculation using PCWSTR.len().

Using the built-in len() method is cleaner and more reliable than manual length calculation.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/opc_da

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.

@coderabbitai coderabbitai bot changed the title @coderabbitai Add opc_comn crate and pointer/memory macros to opc_classic_utils Jul 24, 2025
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 (3)
opc_comn/.gitignore (1)

1-1: Consolidate ignore rules at the workspace root

Ignoring target/ is useful, but placing the rule in every crate quickly becomes repetitive.
If you already have a workspace-level .gitignore, consider keeping the rule there and deleting this file to reduce duplication.
Otherwise, no harm leaving it here.

Cargo.toml (1)

6-7: Workspace member entry OK

opc_comn is correctly added to the member list and the dependency table.
Ensure every crate that depends on it uses { workspace = true } rather than another direct path to avoid version drift.

opc_comn/README.md (1)

1-9: Minor wording & spelling polish

  • “OPC Comn” → “OPC Common” for clarity.
  • Consider collapsing the two bold warnings into a single paragraph to reduce redundancy.
-# OPC Comn
+# OPC Common
@@
-**Warning: This library is still under development and may not be fully functional. Use at your own risk.**
-
-**Note: The API is unstable, tests are incomplete, and there may be unsafe memory operations.**
+**Warning:** This library is under active development; the API may change, tests are incomplete, and some unsafe memory operations are still being reviewed. Use at your own risk.
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e4f25e1 and e58d709.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml (2 hunks)
  • opc_classic_utils/README.md (1 hunks)
  • opc_classic_utils/examples/macro_usage.rs (1 hunks)
  • opc_classic_utils/src/memory/array.rs (2 hunks)
  • opc_classic_utils/src/memory/ptr.rs (2 hunks)
  • opc_classic_utils/src/memory/wstring.rs (5 hunks)
  • opc_comn/.gitignore (1 hunks)
  • opc_comn/Cargo.toml (1 hunks)
  • opc_comn/README.md (1 hunks)
  • opc_comn/src/lib.rs (1 hunks)
  • opc_comn/src/opc_common.rs (1 hunks)
  • opc_da/Cargo.toml (1 hunks)
🧰 Additional context used
🧠 Learnings (10)
opc_comn/src/lib.rs (1)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

opc_comn/README.md (3)

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_da/Cargo.toml (2)

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_comn/Cargo.toml (1)

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

opc_classic_utils/src/memory/array.rs (6)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_properties.rs:58-62
Timestamp: 2024-12-11T01:41:07.039Z
Learning: When count is of type u32, it's safe to set array lengths without additional bounds checking.

opc_classic_utils/src/memory/ptr.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

opc_classic_utils/README.md (1)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

opc_classic_utils/examples/macro_usage.rs (1)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

opc_comn/src/opc_common.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

opc_classic_utils/src/memory/wstring.rs (3)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

🧬 Code Graph Analysis (2)
opc_classic_utils/src/memory/array.rs (2)
opc_classic_utils/src/memory/ptr.rs (2)
  • as_ptr (58-60)
  • as_ptr (178-180)
opc_classic_utils/src/memory/ptr_array.rs (2)
  • as_ptr (78-80)
  • as_ptr (253-255)
opc_classic_utils/src/memory/ptr.rs (3)
opc_classic_utils/src/memory/array.rs (2)
  • from_raw (27-29)
  • from_raw (241-243)
opc_classic_utils/src/memory/wstring.rs (2)
  • from_raw (30-32)
  • from_raw (206-208)
opc_classic_utils/src/memory/ptr_array.rs (2)
  • from_raw (27-29)
  • from_raw (248-250)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyzing
🔇 Additional comments (19)
opc_da/Cargo.toml (1)

8-8: Keyword addition looks good

Adding opcda improves discoverability and still keeps the list under the crates.io limit of five keywords.
No further action required.

opc_comn/Cargo.toml (1)

1-20: LGTM! Well-structured Cargo.toml for OPC Common crate.

The configuration is appropriate for an OPC Classic implementation:

  • Rust 2024 edition is current and suitable
  • Windows MSVC target aligns with OPC's Windows-specific nature
  • Workspace dependencies maintain consistency across the project
  • MIT license and descriptive metadata are well-chosen
opc_classic_utils/src/memory/ptr.rs (2)

106-108: Good alignment with COM memory management conventions.

The updated Drop implementation correctly preserves the pointer for the callee to access and free, which is the expected behavior for caller-allocated memory in COM interfaces.


284-295: Excellent macro design with proper safety and error handling.

The write_caller_allocated_ptr! macro provides a clean abstraction that:

  • Safely wraps raw pointers using CallerAllocatedPtr::from_raw
  • Handles null pointer cases with appropriate COM error (E_INVALIDARG)
  • Maintains unsafe context to remind users of safety requirements
  • Includes comprehensive documentation with usage examples

The implementation correctly delegates to the underlying pointer management while providing ergonomic error handling.

opc_classic_utils/README.md (1)

90-124: Excellent documentation for the new convenience macros.

The new "Convenience Macros" section provides:

  • Clear purpose statements for each macro
  • Practical usage examples with proper error handling
  • Consistent formatting that matches the existing documentation style
  • Good coverage of the three main COM memory management scenarios

This documentation will help users adopt the new macros effectively while understanding the underlying memory management patterns.

opc_classic_utils/src/memory/array.rs (2)

165-169: Consistent Drop behavior with caller-allocated memory conventions.

The updated Drop implementation correctly preserves the pointer while clearing the length, maintaining consistency with the CallerAllocatedPtr<T> changes and COM memory management patterns.


404-411: Well-designed macro with excellent composition.

The write_caller_allocated_array! macro demonstrates good software engineering principles:

  • Reuses existing functionality (write_caller_allocated_ptr! and CallerAllocatedArray::from_slice)
  • Maintains consistent error handling patterns
  • Provides comprehensive documentation including memory management details
  • Handles the complex array allocation and pointer writing in a simple interface

The composition approach reduces code duplication and maintains consistency across the macro family.

opc_classic_utils/examples/macro_usage.rs (1)

1-115: Comprehensive and well-structured example demonstrating macro usage.

This example file excels in several areas:

Educational Value:

  • Progresses from simple macro usage to complex OPC scenarios
  • Each example is clearly commented and demonstrates different use cases
  • Shows practical applications like locale ID handling and error string allocation

Code Quality:

  • Proper error handling with ? operator throughout
  • Clean separation between demonstration and testing code
  • Clear console output for interactive learning

Testing Coverage:

  • Unit tests validate each macro's core functionality
  • Tests cover successful cases and verify expected outcomes
  • Follows good testing practices without unnecessary complexity

The OPC Common interface simulation (Example 4) is particularly valuable as it shows how the macros work together in realistic scenarios.

opc_comn/src/opc_common.rs (6)

1-4: LGTM! Well-structured COM interface wrapper.

The generic struct with the 'static bound is appropriate for COM objects that require stable memory locations.


7-13: LGTM! Clean delegation pattern.

Both SetLocaleID and GetLocaleID correctly delegate to their corresponding trait methods.


15-25: LGTM! Proper handling of COM output parameters.

The method correctly uses utility macros to safely write count and array data to caller-allocated pointers, with appropriate error handling for the length conversion.


27-32: LGTM! Correct COM string allocation pattern.

The method properly uses the alloc_callee_wstring! macro to allocate and return a wide string following COM conventions.


34-40: LGTM! Safe handling of wide string input.

The method correctly converts the wide string pointer to a Rust String with appropriate error handling, returning E_POINTER for invalid inputs.


43-51: LGTM! Clean trait design.

The trait provides a safe Rust interface with appropriate error handling through Result types, abstracting away raw pointer handling from implementors.

opc_classic_utils/src/memory/wstring.rs (5)

5-5: LGTM! Required import for new functionality.

Adding PWSTR import is necessary for the new as_pwstr() methods.


132-135: LGTM! Consistent API for mutable pointer access.

The as_pwstr() methods complement the existing as_pcwstr() methods, providing access to mutable wide string pointers when needed.

Also applies to: 284-287


141-142: LGTM! Clarifying comments improve code understanding.

The updated comments clearly explain why the pointer is preserved in the Drop implementation, which is correct for caller-allocated memory patterns.


217-221: LGTM! Convenient string conversion method.

The from_string method provides API consistency with CallerAllocatedWString and correctly delegates to the FromStr implementation.


341-397: LGTM! Well-designed and documented macro.

The alloc_callee_wstring! macro provides a clean API for COM string allocation with comprehensive documentation and examples. The implementation correctly leverages the FromStr trait.

Comment on lines +321 to +339
impl std::str::FromStr for CalleeAllocatedWString {
type Err = windows::core::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let wide_string: Vec<u16> = OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let len = wide_string.len() - 1; // Exclude null terminator for allocation
let ptr = unsafe { CoTaskMemAlloc(len * std::mem::size_of::<u16>()) };
if ptr.is_null() {
return Err(windows::core::Error::from_win32());
}
unsafe {
std::ptr::copy_nonoverlapping(wide_string.as_ptr(), ptr.cast(), wide_string.len());
}
Ok(unsafe { Self::new(ptr.cast()) })
}
}
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 buffer overflow in memory allocation.

The memory allocation on line 330 doesn't account for the null terminator, causing a buffer overflow when copying the entire wide_string (which includes the null terminator).

Apply this fix:

-        let ptr = unsafe { CoTaskMemAlloc(len * std::mem::size_of::<u16>()) };
+        let ptr = unsafe { CoTaskMemAlloc((len + 1) * std::mem::size_of::<u16>()) };

The allocation should include space for the null terminator since wide_string.len() includes it but len doesn't.

📝 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
impl std::str::FromStr for CalleeAllocatedWString {
type Err = windows::core::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let wide_string: Vec<u16> = OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let len = wide_string.len() - 1; // Exclude null terminator for allocation
let ptr = unsafe { CoTaskMemAlloc(len * std::mem::size_of::<u16>()) };
if ptr.is_null() {
return Err(windows::core::Error::from_win32());
}
unsafe {
std::ptr::copy_nonoverlapping(wide_string.as_ptr(), ptr.cast(), wide_string.len());
}
Ok(unsafe { Self::new(ptr.cast()) })
}
}
impl std::str::FromStr for CalleeAllocatedWString {
type Err = windows::core::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let wide_string: Vec<u16> = OsStr::new(s)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let len = wide_string.len() - 1; // Exclude null terminator for allocation
let ptr = unsafe { CoTaskMemAlloc((len + 1) * std::mem::size_of::<u16>()) };
if ptr.is_null() {
return Err(windows::core::Error::from_win32());
}
unsafe {
std::ptr::copy_nonoverlapping(wide_string.as_ptr(), ptr.cast(), wide_string.len());
}
Ok(unsafe { Self::new(ptr.cast()) })
}
}
🤖 Prompt for AI Agents
In opc_classic_utils/src/memory/wstring.rs lines 321 to 339, the memory
allocation size for the wide string does not include space for the null
terminator, causing a buffer overflow during copy. Fix this by allocating memory
for wide_string.len() elements instead of len, ensuring the null terminator is
included in the allocated buffer size.

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

clippy found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

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: 7

🧹 Nitpick comments (7)
opc_classic_utils/src/memory/ptr_array.rs (1)

276-279: Fix documentation inconsistency

The comment states "freed by the caller" but this is a CalleeAllocatedPtrArray where memory should be "freed by the callee".

 /// Allocates memory for a pointer array using `CoTaskMemAlloc` and creates a `CalleeAllocatedPtrArray`
 ///
-/// This allocates memory that will be freed by the caller (COM function).
-/// The callee is responsible for ensuring the caller will free this memory.
+/// This allocates memory that will be freed by the caller.
+/// The callee (COM function) is responsible for returning this memory to the caller who will free it.
opc_da/src/opc_data_callback.rs (1)

24-50: Consider using slices instead of Vec to avoid unnecessary allocations

The current implementation converts slices to Vec, which causes unnecessary heap allocations. Since the data is already in memory, the trait methods could accept slices instead.

Consider modifying the trait to accept slices:

pub trait OPCDataCallback {
    fn on_data_change(
        &self,
        transaction_id: u32,
        group: u32,
        quality: HRESULT,
        error: HRESULT,
        client_items: &[u32],
        values: &[windows::Win32::System::Variant::VARIANT],
        qualities: &[u16],
        timestamps: &[windows::Win32::Foundation::FILETIME],
        errors: &[HRESULT],
    ) -> Result<()>;
    // ... other methods
}

This would eliminate the .to_vec() calls and improve performance for large datasets.

opc_da/src/opc_browse.rs (1)

93-95: Browse continuation point not implemented.

The pszcontinuationpoint parameter is always set to null, which means browse operations cannot be continued if there are more elements than dwmaxelementsreturned.

Consider implementing continuation point support if the underlying trait supports it, or document this limitation in the trait definition.

opc_da/src/opc_group.rs (1)

185-202: Unused trait methods in OPCGroupStateMgt2.

The trait defines get_state2 and set_state2 methods that are never called in the implementation. These seem to be extended versions that include the keep-alive parameter.

Consider whether these methods are needed. If they're part of a future implementation, add a TODO comment. Otherwise, remove them to simplify the trait.

opc_da/src/opc_sync_io.rs (3)

1-3: Consider using specific imports instead of wildcards.

For better code clarity and to avoid namespace pollution, consider importing only the specific types and functions needed.


26-41: Consider using memory management macros for consistency.

The codebase provides memory management macros in opc_classic_utils. Consider using write_caller_allocated_array! for allocating and writing the item states array.

-        // Allocate and write item states
-        if !ppitemvalues.is_null() {
-            let size = std::mem::size_of::<tagOPCITEMSTATE>() * item_states.len();
-            let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
-            if ptr.is_null() {
-                return Err(windows::core::Error::from_win32());
-            }
-            
-            unsafe {
-                std::ptr::copy_nonoverlapping(
-                    item_states.as_ptr(),
-                    ptr.cast(),
-                    item_states.len()
-                );
-                *ppitemvalues = ptr.cast();
-            }
-        }
+        // Allocate and write item states
+        write_caller_allocated_array!(ppitemvalues, item_states)?;

122-138: Use memory management macros for array allocations in ReadMaxAge.

Similar to the previous suggestion, consider using the memory management macros for these allocations as well.

-        // Allocate and write values
-        if !ppvvalues.is_null() {
-            let size = std::mem::size_of::<windows::Win32::System::Variant::VARIANT>() * values.len();
-            let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
-            if ptr.is_null() {
-                return Err(windows::core::Error::from_win32());
-            }
-            
-            unsafe {
-                std::ptr::copy_nonoverlapping(
-                    values.as_ptr(),
-                    ptr.cast(),
-                    values.len()
-                );
-                *ppvvalues = ptr.cast();
-            }
-        }
+        // Allocate and write values
+        write_caller_allocated_array!(ppvvalues, values)?;

Apply similar changes to qualities and timestamps allocations.

Also applies to: 140-156, 158-174

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e58d709 and 55b543b.

📒 Files selected for processing (14)
  • opc_classic_utils/src/memory/array.rs (2 hunks)
  • opc_classic_utils/src/memory/ptr.rs (3 hunks)
  • opc_classic_utils/src/memory/ptr_array.rs (2 hunks)
  • opc_classic_utils/src/memory/wstring.rs (6 hunks)
  • opc_comn/src/lib.rs (1 hunks)
  • opc_comn/src/opc_common.rs (1 hunks)
  • opc_da/src/lib.rs (1 hunks)
  • opc_da/src/opc_async_io.rs (1 hunks)
  • opc_da/src/opc_browse.rs (1 hunks)
  • opc_da/src/opc_data_callback.rs (1 hunks)
  • opc_da/src/opc_group.rs (1 hunks)
  • opc_da/src/opc_item.rs (1 hunks)
  • opc_da/src/opc_server.rs (1 hunks)
  • opc_da/src/opc_sync_io.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • opc_da/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • opc_comn/src/lib.rs
  • opc_classic_utils/src/memory/wstring.rs
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.
opc_classic_utils/src/memory/ptr.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

opc_da/src/opc_server.rs (7)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

opc_classic_utils/src/memory/array.rs (6)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_properties.rs:58-62
Timestamp: 2024-12-11T01:41:07.039Z
Learning: When count is of type u32, it's safe to set array lengths without additional bounds checking.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

opc_classic_utils/src/memory/ptr_array.rs (5)

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

opc_comn/src/opc_common.rs (5)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

opc_da/src/opc_item.rs (8)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_da/src/opc_data_callback.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

opc_da/src/opc_async_io.rs (7)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

opc_da/src/opc_browse.rs (5)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

opc_da/src/opc_sync_io.rs (5)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

opc_da/src/opc_group.rs (7)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

🧬 Code Graph Analysis (5)
opc_classic_utils/src/memory/array.rs (2)
opc_classic_utils/src/memory/ptr_array.rs (12)
  • as_ptr (80-82)
  • as_ptr (305-307)
  • from_raw (29-31)
  • from_raw (250-252)
  • len (96-98)
  • len (321-323)
  • is_null (106-108)
  • is_null (331-333)
  • new (24-26)
  • new (243-245)
  • as_mut_slice (128-134)
  • as_mut_slice (353-359)
opc_classic_utils/src/memory/ptr.rs (8)
  • as_ptr (58-60)
  • as_ptr (175-177)
  • from_raw (27-29)
  • from_raw (155-157)
  • is_null (72-74)
  • is_null (189-191)
  • new (22-24)
  • new (148-150)
opc_da/src/opc_async_io.rs (1)
opc_da/src/opc_sync_io.rs (16)
  • Read (11-62)
  • std (27-27)
  • std (45-45)
  • std (82-82)
  • std (124-124)
  • std (142-142)
  • std (160-160)
  • std (178-178)
  • std (215-215)
  • Write (64-99)
  • read (239-239)
  • write (241-241)
  • ReadMaxAge (103-195)
  • WriteVQT (197-232)
  • read_max_age (245-245)
  • write_vqt (247-247)
opc_da/src/opc_browse.rs (3)
opc_classic_utils/src/memory/ptr_array.rs (3)
  • std (45-45)
  • std (288-288)
  • from_slice (255-274)
opc_classic_utils/src/memory/wstring.rs (4)
  • std (52-52)
  • std (337-337)
  • from_pcwstr (35-39)
  • from_pcwstr (218-222)
opc_classic_utils/src/memory/array.rs (2)
  • std (43-43)
  • from_slice (60-76)
opc_da/src/opc_sync_io.rs (1)
opc_da/src/opc_async_io.rs (18)
  • Read (11-47)
  • Read (111-146)
  • std (30-30)
  • std (71-71)
  • std (129-129)
  • std (170-170)
  • std (234-234)
  • std (275-275)
  • Write (49-88)
  • Write (148-187)
  • ReadMaxAge (212-251)
  • WriteVQT (253-292)
  • read (307-307)
  • read (317-317)
  • write (309-309)
  • write (319-319)
  • read_max_age (331-331)
  • write_vqt (333-333)
opc_da/src/opc_group.rs (1)
opc_classic_utils/src/memory/wstring.rs (2)
  • from_pcwstr (35-39)
  • from_pcwstr (218-222)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyzing
🔇 Additional comments (11)
opc_classic_utils/src/memory/ptr.rs (3)

103-109: LGTM! Clear documentation of memory ownership semantics

The updated Drop implementation and comments clearly document that the pointer is intentionally preserved for the callee to use, which aligns with the caller-allocated memory pattern in COM.


162-162: Good improvement to API flexibility

Removing the T: Copy constraint from from_value makes the method more flexible while maintaining safety through copy_nonoverlapping.


281-292: Well-designed convenience macro

The write_caller_allocated_ptr! macro provides a safe and ergonomic way to write values to caller-allocated pointers with proper error handling.

opc_da/src/opc_server.rs (2)

11-58: LGTM! Well-structured COM interface implementation

The AddGroup implementation properly handles:

  • Safe string conversion from PCWSTR
  • Optional pointer parameters with null checks
  • Output parameter writing using the convenience macros
  • Proper error propagation

82-86: LGTM! Correct memory management for COM return values

The GetStatus method correctly allocates COM memory for the return value and transfers ownership to the caller.

opc_classic_utils/src/memory/array.rs (3)

162-170: LGTM! Consistent memory ownership semantics

The Drop implementation correctly preserves the pointer for the callee while clearing the length, consistent with the CallerAllocatedPtr behavior.


403-411: Elegant macro composition

The write_caller_allocated_array! macro cleanly composes existing functionality by combining CallerAllocatedArray::from_slice with write_caller_allocated_ptr!.


457-478: Well-designed direct copy macro

The copy_to_caller_array! macro provides safe direct memory copying with proper null checks and error handling. Good use of copy_from_slice for efficient copying.

opc_da/src/opc_group.rs (1)

87-105: Unused return values from set_state method.

The set_state method returns 6 values but only revised_update_rate is used. The other values are ignored, which might indicate missing functionality.

Either use all returned values or modify the trait to only return what's needed:

-        let (
-            revised_update_rate,
-            new_active,
-            new_time_bias,
-            new_percent_deadband,
-            new_locale_id,
-            new_client_group,
-        ) = self.0.set_state(
+        let revised_update_rate = self.0.set_state(
             requested_update_rate,
             active,
             time_bias,
             percent_deadband,
             locale_id,
             client_group,
         )?;

Alternatively, if the COM interface requires these values to be returned, they should be written back to the caller through the corresponding output pointers.

opc_comn/src/opc_common.rs (1)

93-119: Optimize array handling and reduce code duplication.

The current implementation creates unnecessary intermediate vectors and duplicates code between OPCServerList and OPCServerList2.

Extract a helper function:

fn guid_array_to_vec(ptr: *const windows_core::GUID, count: u32) -> Vec<windows_core::GUID> {
    if ptr.is_null() || count == 0 {
        Vec::new()
    } else {
        unsafe {
            std::slice::from_raw_parts(ptr, count as usize).to_vec()
        }
    }
}

Then simplify both implementations:

-        let implemented = if !rgcatidimpl.is_null() {
-            unsafe {
-                opc_classic_utils::CallerAllocatedArray::new(
-                    rgcatidimpl as *mut windows_core::GUID,
-                    cimplemented as usize,
-                )
-                .as_slice()
-                .unwrap_or(&[])
-                .to_vec()
-            }
-        } else {
-            Vec::new()
-        };
+        let implemented = guid_array_to_vec(rgcatidimpl, cimplemented);

Also applies to: 171-197

⛔ Skipped due to learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the `add_items` method in `opc_da/src/client/unified/group.rs`, the OPC DA protocol guarantees that the `results` and `errors` arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file `opc_da/src/client/tests.rs`, do not add retry logic in test functions as per the user's preference.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In `opc_da/src/client/traits/item_mgt.rs`, the `CreateEnumerator` method returns a `Result`, so there is no need to handle `None` values.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
opc_da/src/opc_sync_io.rs (1)

235-249: Clean trait design!

The trait definitions follow a clear hierarchy with OPCSyncIO2 extending OPCSyncIO, and the method signatures are well-designed for Rust usage.

Comment on lines +254 to +274
/// Creates a new `CalleeAllocatedPtrArray` from a slice
pub fn from_slice(slice: &[T]) -> Result<Self, windows::core::Error> {
if slice.is_empty() {
return Ok(Self {
ptr: ptr::null_mut(),
len: 0,
});
}
let array = Self::allocate(slice.len())?;

// convert items to CalleeAllocatedPtr write to array
for (i, item) in slice.iter().enumerate() {
let item_ptr = CalleeAllocatedPtr::from_value(item)?;
if item_ptr.is_null() {
return Err(windows::core::Error::from_win32());
}
unsafe { *array.ptr.add(i) = item_ptr.as_ptr() };
}

Ok(array)
}
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 memory leak in error handling

The from_slice method has a memory leak issue. If CalleeAllocatedPtr::from_value fails after successfully allocating some items, those previously allocated pointers won't be freed. Additionally, the error check at line 268 doesn't properly propagate the error from from_value.

Apply this fix to handle errors properly and prevent memory leaks:

 pub fn from_slice(slice: &[T]) -> Result<Self, windows::core::Error> {
     if slice.is_empty() {
         return Ok(Self {
             ptr: ptr::null_mut(),
             len: 0,
         });
     }
     let array = Self::allocate(slice.len())?;

     // convert items to CalleeAllocatedPtr write to array
     for (i, item) in slice.iter().enumerate() {
-        let item_ptr = CalleeAllocatedPtr::from_value(item)?;
-        if item_ptr.is_null() {
-            return Err(windows::core::Error::from_win32());
+        match CalleeAllocatedPtr::from_value(item) {
+            Ok(item_ptr) => {
+                unsafe { *array.ptr.add(i) = item_ptr.into_raw() };
+            }
+            Err(e) => {
+                // Clean up previously allocated items
+                unsafe {
+                    for j in 0..i {
+                        let ptr = *array.ptr.add(j);
+                        if !ptr.is_null() {
+                            CoTaskMemFree(Some(ptr.cast()));
+                        }
+                    }
+                    // Clean up the array itself
+                    CoTaskMemFree(Some(array.ptr.cast()));
+                }
+                return Err(e);
+            }
         }
-        unsafe { *array.ptr.add(i) = item_ptr.as_ptr() };
     }

     Ok(array)
 }
📝 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
/// Creates a new `CalleeAllocatedPtrArray` from a slice
pub fn from_slice(slice: &[T]) -> Result<Self, windows::core::Error> {
if slice.is_empty() {
return Ok(Self {
ptr: ptr::null_mut(),
len: 0,
});
}
let array = Self::allocate(slice.len())?;
// convert items to CalleeAllocatedPtr write to array
for (i, item) in slice.iter().enumerate() {
let item_ptr = CalleeAllocatedPtr::from_value(item)?;
if item_ptr.is_null() {
return Err(windows::core::Error::from_win32());
}
unsafe { *array.ptr.add(i) = item_ptr.as_ptr() };
}
Ok(array)
}
/// Creates a new `CalleeAllocatedPtrArray` from a slice
pub fn from_slice(slice: &[T]) -> Result<Self, windows::core::Error> {
if slice.is_empty() {
return Ok(Self {
ptr: ptr::null_mut(),
len: 0,
});
}
let array = Self::allocate(slice.len())?;
// convert items to CalleeAllocatedPtr write to array
for (i, item) in slice.iter().enumerate() {
match CalleeAllocatedPtr::from_value(item) {
Ok(item_ptr) => {
unsafe { *array.ptr.add(i) = item_ptr.into_raw() };
}
Err(e) => {
// Clean up previously allocated items
unsafe {
for j in 0..i {
let ptr = *array.ptr.add(j);
if !ptr.is_null() {
CoTaskMemFree(Some(ptr.cast()));
}
}
// Clean up the array itself
CoTaskMemFree(Some(array.ptr.cast()));
}
return Err(e);
}
}
}
Ok(array)
}
🤖 Prompt for AI Agents
In opc_classic_utils/src/memory/ptr_array.rs lines 254 to 274, the from_slice
method leaks memory if CalleeAllocatedPtr::from_value fails after some
allocations because previously allocated pointers are not freed. To fix this,
track all successfully allocated pointers and on any error, free them before
returning the error. Also, propagate the actual error from from_value instead of
creating a generic one. Implement proper cleanup in the error path to prevent
leaks.

Comment on lines +29 to +44
if !pperrors.is_null() {
let size = std::mem::size_of::<HRESULT>() * errors.len();
let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
if ptr.is_null() {
return Err(windows::core::Error::from_win32());
}

unsafe {
std::ptr::copy_nonoverlapping(
errors.as_ptr(),
ptr.cast(),
errors.len()
);
*pperrors = ptr.cast();
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract repeated error allocation logic into a helper function.

The error array allocation pattern is duplicated across multiple methods. Consider extracting this into a helper function to improve maintainability and reduce code duplication.

Add a helper function:

fn allocate_and_write_errors(pperrors: *mut *mut HRESULT, errors: &[HRESULT]) -> Result<()> {
    if !pperrors.is_null() {
        let size = std::mem::size_of::<HRESULT>() * errors.len();
        let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
        if ptr.is_null() {
            return Err(windows::core::Error::from_win32());
        }
        
        unsafe {
            std::ptr::copy_nonoverlapping(
                errors.as_ptr(),
                ptr.cast(),
                errors.len()
            );
            *pperrors = ptr.cast();
        }
    }
    Ok(())
}

Then simplify the methods:

-        // Allocate and write errors
-        if !pperrors.is_null() {
-            let size = std::mem::size_of::<HRESULT>() * errors.len();
-            let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
-            if ptr.is_null() {
-                return Err(windows::core::Error::from_win32());
-            }
-            
-            unsafe {
-                std::ptr::copy_nonoverlapping(
-                    errors.as_ptr(),
-                    ptr.cast(),
-                    errors.len()
-                );
-                *pperrors = ptr.cast();
-            }
-        }
+        allocate_and_write_errors(pperrors, &errors)?;

Also applies to: 70-85

🤖 Prompt for AI Agents
In opc_da/src/opc_async_io.rs around lines 29 to 44 and also lines 70 to 85, the
code for allocating and writing error arrays is duplicated. Extract this
repeated logic into a new helper function named allocate_and_write_errors that
takes pperrors and errors as parameters, performs the allocation and copying,
and returns a Result. Then replace the duplicated code in these methods by calls
to this helper function to improve maintainability and reduce duplication.

Comment on lines +306 to +328
pub trait OPCAsyncIO {
fn read(&self, connection: u32, source: tagOPCDATASOURCE, server_handles: Vec<u32>) -> Result<(u32, Vec<HRESULT>)>;

fn write(&self, connection: u32, server_handles: Vec<u32>, values: Vec<windows::Win32::System::Variant::VARIANT>) -> Result<(u32, Vec<HRESULT>)>;

fn refresh(&self, connection: u32, source: tagOPCDATASOURCE) -> Result<u32>;

fn cancel(&self, transaction_id: u32) -> Result<()>;
}

pub trait OPCAsyncIO2: OPCAsyncIO {
fn read(&self, count: u32, server_handles: Vec<u32>, transaction_id: u32) -> Result<(u32, Vec<HRESULT>)>;

fn write(&self, count: u32, server_handles: Vec<u32>, values: Vec<windows::Win32::System::Variant::VARIANT>, transaction_id: u32) -> Result<(u32, Vec<HRESULT>)>;

fn refresh2(&self, source: tagOPCDATASOURCE, transaction_id: u32) -> Result<u32>;

fn cancel2(&self, cancel_id: u32) -> Result<()>;

fn set_enable(&self, enable: bool) -> Result<()>;

fn get_enable(&self) -> Result<bool>;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential naming conflict in trait hierarchy.

The OPCAsyncIO and OPCAsyncIO2 traits both define methods named read, write, refresh, and cancel with different signatures. Since OPCAsyncIO2 extends OPCAsyncIO, this creates ambiguity and requires fully qualified syntax when calling these methods.

Consider renaming the methods in one of the traits to avoid confusion:

 pub trait OPCAsyncIO {
-    fn read(&self, connection: u32, source: tagOPCDATASOURCE, server_handles: Vec<u32>) -> Result<(u32, Vec<HRESULT>)>;
+    fn read_async(&self, connection: u32, source: tagOPCDATASOURCE, server_handles: Vec<u32>) -> Result<(u32, Vec<HRESULT>)>;
     
-    fn write(&self, connection: u32, server_handles: Vec<u32>, values: Vec<windows::Win32::System::Variant::VARIANT>) -> Result<(u32, Vec<HRESULT>)>;
+    fn write_async(&self, connection: u32, server_handles: Vec<u32>, values: Vec<windows::Win32::System::Variant::VARIANT>) -> Result<(u32, Vec<HRESULT>)>;
     
-    fn refresh(&self, connection: u32, source: tagOPCDATASOURCE) -> Result<u32>;
+    fn refresh_async(&self, connection: u32, source: tagOPCDATASOURCE) -> Result<u32>;
     
-    fn cancel(&self, transaction_id: u32) -> Result<()>;
+    fn cancel_async(&self, transaction_id: u32) -> Result<()>;
 }
🤖 Prompt for AI Agents
In opc_da/src/opc_async_io.rs between lines 306 and 328, the traits OPCAsyncIO
and OPCAsyncIO2 both define methods named read, write, refresh, and cancel but
with different parameters, causing naming conflicts and ambiguity. To fix this,
rename the methods in OPCAsyncIO2 to distinct names that do not overlap with
OPCAsyncIO, such as read2, write2, refresh2 (already done), and cancel2, or use
more descriptive names to clearly differentiate them and avoid the need for
fully qualified syntax when calling these methods.

Comment on lines +38 to +41
if !ppitemproperties.is_null() {
let properties_array = CallerAllocatedArray::from_slice(&properties)?;
unsafe { *ppitemproperties = properties_array.as_ptr() };
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Potential use-after-free with CallerAllocatedArray.

The CallerAllocatedArray is created but goes out of scope after as_ptr() is called, potentially leading to use-after-free.

The CallerAllocatedArray should transfer ownership. Check if it has an into_raw() method or similar:

-            let properties_array = CallerAllocatedArray::from_slice(&properties)?;
-            unsafe { *ppitemproperties = properties_array.as_ptr() };
+            let properties_array = CallerAllocatedArray::from_slice(&properties)?;
+            unsafe { *ppitemproperties = properties_array.into_raw() };
🤖 Prompt for AI Agents
In opc_da/src/opc_browse.rs around lines 38 to 41, the CallerAllocatedArray is
created and its pointer assigned, but the array goes out of scope immediately
causing a potential use-after-free. To fix this, transfer ownership of the
CallerAllocatedArray by using its into_raw() method or equivalent instead of
as_ptr(), ensuring the allocated memory remains valid after the function
returns.

Comment on lines 23 to 32
access_path: unsafe {
CallerAllocatedWString::from_pwstr(item.szAccessPath)
.to_string()
.unwrap_or_default()
},
item_id: unsafe {
CallerAllocatedWString::from_pwstr(item.szItemID)
.to_string()
.unwrap_or_default()
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent string conversion methods between AddItems and ValidateItems.

AddItems uses from_pwstr while ValidateItems uses from_pcwstr with explicit null checks. This inconsistency could lead to maintenance issues.

Standardize the approach across both methods. Since ValidateItems has proper null checks, apply the same pattern to AddItems:

                 access_path: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szAccessPath)
-                        .to_string()
-                        .unwrap_or_default()
+                    if item.szAccessPath.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szAccessPath.0))
+                            .to_string()
+                            .unwrap_or_default()
+                    }
                 },
                 item_id: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szItemID)
-                        .to_string()
-                        .unwrap_or_default()
+                    if item.szItemID.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szItemID.0))
+                            .to_string()
+                            .unwrap_or_default()
+                    }
                 },

Also applies to: 74-89

🤖 Prompt for AI Agents
In opc_da/src/opc_item.rs around lines 23 to 32 and also lines 74 to 89, the
string conversion methods are inconsistent between AddItems and ValidateItems;
AddItems uses from_pwstr without null checks, while ValidateItems uses
from_pcwstr with explicit null checks. To fix this, standardize by replacing
from_pwstr with from_pcwstr in AddItems and add explicit null checks before
conversion, mirroring the pattern used in ValidateItems to ensure consistent and
safe string handling.

Comment on lines 49 to 56
// Allocate and write results
let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
unsafe { *ppaddresults = results_ptr.as_ptr() };

// Allocate and write errors
let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
unsafe { *pperrors = errors_ptr.as_ptr() };

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Memory leak due to incorrect CalleeAllocatedPtrArray usage.

The CalleeAllocatedPtrArray is created but the ownership is not properly transferred. The as_ptr() method returns a raw pointer, but the CalleeAllocatedPtrArray will be dropped at the end of the scope, leaving dangling pointers.

Use into_raw() instead of as_ptr():

-        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
-        unsafe { *ppaddresults = results_ptr.as_ptr() };
+        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
+        unsafe { *ppaddresults = results_ptr.into_raw() };

-        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
-        unsafe { *pperrors = errors_ptr.as_ptr() };
+        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
+        unsafe { *pperrors = errors_ptr.into_raw() };
🤖 Prompt for AI Agents
In opc_da/src/opc_item.rs around lines 49 to 56, the code uses
CalleeAllocatedPtrArray::from_slice to create arrays and assigns raw pointers
using as_ptr(), but this causes a memory leak because the
CalleeAllocatedPtrArray is dropped and the pointers become dangling. To fix
this, replace as_ptr() with into_raw() to transfer ownership of the allocated
memory properly and prevent it from being freed prematurely.

Comment on lines 44 to 59
if !pperrors.is_null() {
let size = std::mem::size_of::<HRESULT>() * errors.len();
let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
if ptr.is_null() {
return Err(windows::core::Error::from_win32());
}

unsafe {
std::ptr::copy_nonoverlapping(
errors.as_ptr(),
ptr.cast(),
errors.len()
);
*pperrors = ptr.cast();
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract repeated error allocation logic.

The error allocation pattern is duplicated four times. Consider extracting it into a helper function or using the write_caller_allocated_array! macro.

Extract the error allocation logic into a helper function:

fn write_errors(pperrors: *mut *mut HRESULT, errors: &[HRESULT]) -> Result<()> {
    write_caller_allocated_array!(pperrors, errors)?;
    Ok(())
}

Then replace all occurrences with:

-        // Allocate and write errors
-        if !pperrors.is_null() {
-            let size = std::mem::size_of::<HRESULT>() * errors.len();
-            let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) };
-            if ptr.is_null() {
-                return Err(windows::core::Error::from_win32());
-            }
-            
-            unsafe {
-                std::ptr::copy_nonoverlapping(
-                    errors.as_ptr(),
-                    ptr.cast(),
-                    errors.len()
-                );
-                *pperrors = ptr.cast();
-            }
-        }
+        // Allocate and write errors
+        write_errors(pperrors, &errors)?;

Also applies to: 81-96, 177-192, 214-229

🤖 Prompt for AI Agents
In opc_da/src/opc_sync_io.rs around lines 44 to 59, the error allocation logic
is duplicated multiple times (also at lines 81-96, 177-192, and 214-229). To fix
this, extract the repeated code into a helper function named write_errors that
takes pperrors and errors as parameters and uses the
write_caller_allocated_array! macro to handle the allocation and copying. Then
replace all the duplicated blocks with calls to this new helper function to
reduce code repetition and improve maintainability.

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: 4

♻️ Duplicate comments (4)
opc_da/src/opc_item.rs (2)

23-32: Inconsistent string conversion methods between AddItems and ValidateItems.


49-56: Critical: Type mismatch errors preventing compilation.

The code has two issues:

  1. Lines 51, 55: Using as_ptr() will cause memory leaks (as noted in past review)
  2. The as_ptr() method returns *mut *mut T but the assignment expects *mut T

Apply this fix to resolve both the memory leak and type mismatch:

-        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
-        unsafe { *ppaddresults = results_ptr.as_ptr() };
+        unsafe { *ppaddresults = CalleeAllocatedPtrArray::from_slice(&results)?.into_raw() };

-        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
-        unsafe { *pperrors = errors_ptr.as_ptr() };
+        unsafe { *pperrors = CalleeAllocatedPtrArray::from_slice(&errors)?.into_raw() };
opc_da/src/opc_browse.rs (1)

38-41: Critical: Potential use-after-free with CallerAllocatedArray.

opc_classic_utils/src/memory/wstring.rs (1)

328-346: Fix buffer overflow in memory allocation.

🧹 Nitpick comments (4)
opc_comn/src/server/traits/opc_shutdown.rs (1)

1-3: Add documentation for the trait and method.

Consider adding documentation to explain the purpose of this trait and when shutdown_request should be called.

+/// Trait for handling OPC server shutdown requests.
 pub trait OpcShutdown {
+    /// Handles a shutdown request with the specified reason.
+    ///
+    /// # Arguments
+    /// * `reason` - The reason for the shutdown request
+    ///
+    /// # Returns
+    /// * `Ok(())` if the shutdown request was handled successfully
+    /// * `Err(...)` if an error occurred
     fn shutdown_request(&self, reason: String) -> windows_core::Result<()>;
 }
opc_classic_utils/examples/convenience_functions.rs (1)

78-128: API change looks good, but consider documenting the lossy conversion behavior.

The consistent change from to_string() to to_string_lossy() aligns with the API updates. However, users of these examples should understand when lossy conversion might occur (e.g., when encountering invalid UTF-16 sequences).

Consider adding a comment explaining the lossy conversion:

 println!("\n2. Converting wide strings back to Rust strings:");
 
 // Convert back to String
 unsafe {
+    // Note: to_string_lossy() replaces invalid UTF-16 sequences with replacement characters
     let converted1 = wstring1.to_string_lossy().unwrap();
opc_comn/src/server/opc_server_base.rs (1)

48-56: Consider extracting common PCWSTR to String conversion logic.

Both SetClientName and ShutdownRequest use identical code for converting PCWSTR to String. Consider extracting this into a helper function.

+fn pcwstr_to_string(pcwstr: &windows_core::PCWSTR) -> windows_core::Result<String> {
+    unsafe {
+        opc_classic_utils::CallerAllocatedWString::from_pcwstr(*pcwstr)
+            .to_string_lossy()
+            .ok_or(windows::Win32::Foundation::E_POINTER)
+    }
+}
+
 impl<T: super::traits::opc_common::OpcCommon + super::traits::opc_shutdown::OpcShutdown>
     opc_comn_bindings::IOPCCommon_Impl for OpcServerBase_Impl<T>
 {
     // ...
     fn SetClientName(&self, szname: &windows_core::PCWSTR) -> windows_core::Result<()> {
-        self.0.set_client_name(unsafe {
-            opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szname)
-                .to_string_lossy()
-                .ok_or(windows::Win32::Foundation::E_POINTER)
-        }?)
+        self.0.set_client_name(pcwstr_to_string(szname)?)
     }
 }

 impl<T: super::traits::opc_common::OpcCommon + super::traits::opc_shutdown::OpcShutdown>
     opc_comn_bindings::IOPCShutdown_Impl for OpcServerBase_Impl<T>
 {
     fn ShutdownRequest(&self, szreason: &windows_core::PCWSTR) -> windows_core::Result<()> {
-        let reason = unsafe {
-            opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szreason)
-                .to_string_lossy()
-                .ok_or(windows::Win32::Foundation::E_POINTER)
-        }?;
-
-        self.0.shutdown_request(reason)
+        self.0.shutdown_request(pcwstr_to_string(szreason)?)
     }
 }
opc_da/src/opc_group.rs (1)

87-104: Fix unused variable warnings.

The destructured variables are not used after assignment.

Apply this fix to suppress warnings:

-        let (
-            revised_update_rate,
-            new_active,
-            new_time_bias,
-            new_percent_deadband,
-            new_locale_id,
-            new_client_group,
-        ) = self.0.set_state(
+        let (
+            revised_update_rate,
+            _new_active,
+            _new_time_bias,
+            _new_percent_deadband,
+            _new_locale_id,
+            _new_client_group,
+        ) = self.0.set_state(
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 55b543b and 9c95168.

📒 Files selected for processing (14)
  • opc_classic_utils/examples/convenience_functions.rs (3 hunks)
  • opc_classic_utils/src/memory/tests.rs (4 hunks)
  • opc_classic_utils/src/memory/wstring.rs (7 hunks)
  • opc_comn/src/client/mod.rs (1 hunks)
  • opc_comn/src/lib.rs (1 hunks)
  • opc_comn/src/server/mod.rs (1 hunks)
  • opc_comn/src/server/opc_server_base.rs (1 hunks)
  • opc_comn/src/server/traits/mod.rs (1 hunks)
  • opc_comn/src/server/traits/opc_common.rs (1 hunks)
  • opc_comn/src/server/traits/opc_shutdown.rs (1 hunks)
  • opc_da/src/opc_browse.rs (1 hunks)
  • opc_da/src/opc_group.rs (1 hunks)
  • opc_da/src/opc_item.rs (1 hunks)
  • opc_da/src/opc_server.rs (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • opc_comn/src/client/mod.rs
  • opc_comn/src/server/mod.rs
  • opc_comn/src/server/traits/mod.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • opc_comn/src/lib.rs
  • opc_da/src/opc_server.rs
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
opc_comn/src/server/traits/opc_shutdown.rs (1)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

opc_classic_utils/examples/convenience_functions.rs (3)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_properties.rs:58-62
Timestamp: 2024-12-11T01:41:07.039Z
Learning: When count is of type u32, it's safe to set array lengths without additional bounds checking.

opc_classic_utils/src/memory/tests.rs (2)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

opc_comn/src/server/traits/opc_common.rs (4)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

opc_comn/src/server/opc_server_base.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_da/src/opc_group.rs (7)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

opc_da/src/opc_browse.rs (8)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

opc_da/src/opc_item.rs (10)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_classic_utils/src/memory/wstring.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

🧬 Code Graph Analysis (3)
opc_comn/src/server/traits/opc_common.rs (1)
opc_da/src/opc_server.rs (1)
  • get_error_string (109-109)
opc_comn/src/server/opc_server_base.rs (3)
opc_da/src/opc_server.rs (2)
  • GetErrorString (60-62)
  • get_error_string (109-109)
opc_comn/src/server/traits/opc_common.rs (1)
  • get_error_string (5-5)
opc_classic_utils/src/memory/wstring.rs (2)
  • from_pcwstr (35-39)
  • from_pcwstr (218-222)
opc_da/src/opc_group.rs (1)
opc_classic_utils/src/memory/wstring.rs (2)
  • from_pcwstr (35-39)
  • from_pcwstr (218-222)
🪛 GitHub Actions: Rust
opc_da/src/opc_group.rs

[warning] 89-89: Unused variable: new_active. Consider prefixing with underscore if intentional.


[warning] 90-90: Unused variable: new_time_bias. Consider prefixing with underscore if intentional.


[warning] 91-91: Unused variable: new_percent_deadband. Consider prefixing with underscore if intentional.


[warning] 92-92: Unused variable: new_locale_id. Consider prefixing with underscore if intentional.


[warning] 93-93: Unused variable: new_client_group. Consider prefixing with underscore if intentional.

opc_da/src/opc_item.rs

[error] 51-51: Rust compiler error E0308: mismatched types. Expected *mut tagOPCITEMRESULT, found *mut *mut tagOPCITEMRESULT.


[error] 55-55: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found *mut *mut HRESULT.


[error] 109-109: Rust compiler error E0308: mismatched types. Expected *mut tagOPCITEMRESULT, found tuple (*mut *mut tagOPCITEMRESULT, usize).


[error] 113-113: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found tuple (*mut *mut HRESULT, usize).


[error] 130-130: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found tuple (*mut *mut HRESULT, usize).


[error] 150-150: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found tuple (*mut *mut HRESULT, usize).


[error] 171-171: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found tuple (*mut *mut HRESULT, usize).


[error] 193-193: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found tuple (*mut *mut HRESULT, usize).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyzing
🔇 Additional comments (6)
opc_classic_utils/src/memory/tests.rs (1)

175-185: Good test coverage for the updated API.

The tests correctly verify both CallerAllocatedWString::to_string_lossy() and CalleeAllocatedWString::to_string(). The differentiation between the two APIs is maintained in the tests.

opc_comn/src/server/opc_server_base.rs (1)

36-42: Good null pointer handling in SetClientName.

The implementation correctly checks for null strings and returns E_POINTER when appropriate. The use of to_string_lossy() is consistent with the API changes.

opc_classic_utils/src/memory/wstring.rs (2)

41-46: LGTM! Consistent PWSTR support added.

The new from_pwstr and as_pwstr methods follow the established patterns for PCWSTR handling.

Also applies to: 139-143, 291-295


145-151: Good clarification on memory ownership.

The updated comments clearly document that the callee is responsible for freeing the memory.

opc_da/src/opc_group.rs (2)

11-39: Well-implemented GetState using new memory macros.

Good use of the write_caller_allocated_ptr! and alloc_callee_wstring! macros with proper null checking.


108-127: Good string handling with proper error checking.

Both SetName and CloneGroup properly handle string conversion with null checks and appropriate error returns.

Comment on lines +1 to +7
pub trait OpcCommon {
fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>;
fn get_locale_id(&self) -> windows_core::Result<u32>;
fn query_available_locale_ids(&self) -> windows_core::Result<Vec<u32>>;
fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result<String>;
fn set_client_name(&self, szname: String) -> windows_core::Result<()>;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add documentation and consider locale parameter for error strings.

The trait lacks documentation. Also, the get_error_string method differs from the similar method in opc_da/src/opc_server.rs which includes a locale parameter.

+/// Trait for OPC Common functionality including locale and client management.
 pub trait OpcCommon {
+    /// Sets the locale identifier for the server.
     fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>;
+    
+    /// Gets the current locale identifier.
     fn get_locale_id(&self) -> windows_core::Result<u32>;
+    
+    /// Returns a list of available locale identifiers.
     fn query_available_locale_ids(&self) -> windows_core::Result<Vec<u32>>;
+    
+    /// Gets a descriptive error string for the given error code.
+    /// Consider whether locale should be a parameter here for consistency with opc_da.
     fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result<String>;
+    
+    /// Sets the client name.
     fn set_client_name(&self, szname: String) -> windows_core::Result<()>;
 }
📝 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
pub trait OpcCommon {
fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>;
fn get_locale_id(&self) -> windows_core::Result<u32>;
fn query_available_locale_ids(&self) -> windows_core::Result<Vec<u32>>;
fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result<String>;
fn set_client_name(&self, szname: String) -> windows_core::Result<()>;
}
/// Trait for OPC Common functionality including locale and client management.
pub trait OpcCommon {
/// Sets the locale identifier for the server.
fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>;
/// Gets the current locale identifier.
fn get_locale_id(&self) -> windows_core::Result<u32>;
/// Returns a list of available locale identifiers.
fn query_available_locale_ids(&self) -> windows_core::Result<Vec<u32>>;
/// Gets a descriptive error string for the given error code.
/// Consider whether locale should be a parameter here for consistency with opc_da.
fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result<String>;
/// Sets the client name.
fn set_client_name(&self, szname: String) -> windows_core::Result<()>;
}
🤖 Prompt for AI Agents
In opc_comn/src/server/traits/opc_common.rs lines 1 to 7, add Rust doc comments
to the OpcCommon trait and its methods to explain their purpose and usage.
Modify the get_error_string method signature to include a locale parameter
similar to the one in opc_da/src/opc_server.rs, allowing error strings to be
localized. Ensure the documentation reflects this change and clarifies the role
of the locale parameter.

Comment on lines +103 to +105
let elements_array = CallerAllocatedArray::from_slice(&elements)?;
unsafe { *ppbrowseelements = elements_array.as_ptr() };
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Potential use-after-free with CallerAllocatedArray.

The CallerAllocatedArray goes out of scope after as_ptr() is called, potentially leading to use-after-free.

Apply the same fix pattern:

-            let elements_array = CallerAllocatedArray::from_slice(&elements)?;
-            unsafe { *ppbrowseelements = elements_array.as_ptr() };
+            unsafe { *ppbrowseelements = CallerAllocatedArray::from_slice(&elements)?.into_raw() };
🤖 Prompt for AI Agents
In opc_da/src/opc_browse.rs around lines 103 to 105, the CallerAllocatedArray is
created and its pointer assigned to ppbrowseelements, but the array goes out of
scope immediately causing a use-after-free. To fix this, store the
CallerAllocatedArray in a variable that lives long enough (e.g., a struct field
or a variable with extended scope) so that the memory remains valid while
ppbrowseelements is used, preventing premature deallocation.

Comment on lines 108 to 114
let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
unsafe { *ppvalidationresults = results_ptr.into_raw() };

// Allocate and write errors
let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
unsafe { *pperrors = errors_ptr.into_raw() };

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Type mismatch - into_raw() returns tuple instead of pointer.

The into_raw() method returns a tuple (*mut *mut T, usize) but the assignment expects just the pointer.

Apply this fix:

-        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
-        unsafe { *ppvalidationresults = results_ptr.into_raw() };
+        unsafe { 
+            let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&results)?.into_raw();
+            *ppvalidationresults = ptr;
+        }

-        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
-        unsafe { *pperrors = errors_ptr.into_raw() };
+        unsafe {
+            let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&errors)?.into_raw();
+            *pperrors = ptr;
+        }
📝 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
let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
unsafe { *ppvalidationresults = results_ptr.into_raw() };
// Allocate and write errors
let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
unsafe { *pperrors = errors_ptr.into_raw() };
unsafe {
let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&results)?.into_raw();
*ppvalidationresults = ptr;
}
// Allocate and write errors
unsafe {
let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&errors)?.into_raw();
*pperrors = ptr;
}
🧰 Tools
🪛 GitHub Actions: Rust

[error] 109-109: Rust compiler error E0308: mismatched types. Expected *mut tagOPCITEMRESULT, found tuple (*mut *mut tagOPCITEMRESULT, usize).


[error] 113-113: Rust compiler error E0308: mismatched types. Expected *mut HRESULT, found tuple (*mut *mut HRESULT, usize).

🤖 Prompt for AI Agents
In opc_da/src/opc_item.rs around lines 108 to 114, the code assigns the result
of into_raw() directly to pointers, but into_raw() returns a tuple of a pointer
and a length. To fix this, destructure the tuple returned by into_raw() and
assign only the pointer part to *ppvalidationresults and *pperrors respectively,
ignoring the length value.

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 (7)
opc_classic_utils/src/memory/wstring.rs (1)

328-346: Fix buffer overflow in memory allocation.

The memory allocation on line 337 doesn't account for the null terminator, causing a buffer overflow when copying the entire wide_string (which includes the null terminator).

Apply this fix:

-        let ptr = unsafe { CoTaskMemAlloc(len * std::mem::size_of::<u16>()) };
+        let ptr = unsafe { CoTaskMemAlloc((len + 1) * std::mem::size_of::<u16>()) };

The allocation should include space for the null terminator since wide_string.len() includes it but len doesn't.

opc_da/src/opc_item.rs (4)

23-32: Add null checks for string conversions.

AddItems uses from_pwstr without null checks while ValidateItems properly checks for null pointers. This inconsistency could lead to undefined behavior if null pointers are passed.

Apply the same null-checking pattern from ValidateItems:

                access_path: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szAccessPath)
-                        .to_string_lossy()
-                        .unwrap_or_default()
+                    if item.szAccessPath.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szAccessPath.0))
+                            .to_string_lossy()
+                            .unwrap_or_default()
+                    }
                },
                item_id: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szItemID)
-                        .to_string_lossy()
-                        .unwrap_or_default()
+                    if item.szItemID.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szItemID.0))
+                            .to_string_lossy()
+                            .unwrap_or_default()
+                    }
                },

49-56: Critical: Memory leak due to incorrect CalleeAllocatedPtrArray usage.

The CalleeAllocatedPtrArray is created but the ownership is not properly transferred. The as_ptr() method returns a raw pointer, but the CalleeAllocatedPtrArray will be dropped at the end of the scope, leaving dangling pointers.

Use into_raw() instead of as_ptr():

-        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
-        unsafe { *ppaddresults = results_ptr.as_ptr() };
+        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
+        unsafe { *ppaddresults = results_ptr.into_raw() };

-        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
-        unsafe { *pperrors = errors_ptr.as_ptr() };
+        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
+        unsafe { *pperrors = errors_ptr.into_raw() };

108-114: Critical: Type mismatch - into_raw() returns tuple instead of pointer.

The into_raw() method returns a tuple (*mut *mut T, usize) but the assignment expects just the pointer.

Apply this fix:

-        let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?;
-        unsafe { *ppvalidationresults = results_ptr.into_raw() };
+        unsafe { 
+            let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&results)?.into_raw();
+            *ppvalidationresults = ptr;
+        }

-        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
-        unsafe { *pperrors = errors_ptr.into_raw() };
+        unsafe {
+            let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&errors)?.into_raw();
+            *pperrors = ptr;
+        }

129-131: Critical: Multiple type mismatches with into_raw() returning tuples.

The same tuple type mismatch occurs in multiple functions.

Apply this pattern to all affected functions:

# In RemoveItems (lines 129-131):
-        let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?;
-        unsafe { *pperrors = errors_ptr.into_raw() };
+        unsafe {
+            let (ptr, _) = CalleeAllocatedPtrArray::from_slice(&errors)?.into_raw();
+            *pperrors = ptr;
+        }

# Apply the same pattern to:
# - SetActiveState (lines 149-151)
# - SetClientHandles (lines 170-172)  
# - SetDatatypes (lines 192-194)
opc_da/src/opc_browse.rs (2)

38-41: Critical: Potential use-after-free with CallerAllocatedArray.

The CallerAllocatedArray is created but goes out of scope after as_ptr() is called, potentially leading to use-after-free.

The CallerAllocatedArray should transfer ownership. Check if it has an into_raw() method or similar:

-            let properties_array = CallerAllocatedArray::from_slice(&properties)?;
-            unsafe { *ppitemproperties = properties_array.as_ptr() };
+            let properties_array = CallerAllocatedArray::from_slice(&properties)?;
+            unsafe { *ppitemproperties = properties_array.into_raw() };

103-105: Critical: Potential use-after-free with CallerAllocatedArray.

The CallerAllocatedArray goes out of scope after as_ptr() is called, potentially leading to use-after-free.

Apply the same fix pattern:

-            let elements_array = CallerAllocatedArray::from_slice(&elements)?;
-            unsafe { *ppbrowseelements = elements_array.as_ptr() };
+            unsafe { *ppbrowseelements = CallerAllocatedArray::from_slice(&elements)?.into_raw() };
🧹 Nitpick comments (1)
opc_classic_utils/src/memory/wstring.rs (1)

83-83: Inconsistent safety documentation.

The method is marked unsafe but uses the safe naming convention to_string_lossy (not prefixed with unsafe_). Consider renaming to unsafe_to_string_lossy for clarity.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9c95168 and c533f56.

📒 Files selected for processing (14)
  • opc_classic_utils/examples/convenience_functions.rs (3 hunks)
  • opc_classic_utils/src/memory/tests.rs (4 hunks)
  • opc_classic_utils/src/memory/wstring.rs (7 hunks)
  • opc_comn/src/client/mod.rs (1 hunks)
  • opc_comn/src/lib.rs (1 hunks)
  • opc_comn/src/server/mod.rs (1 hunks)
  • opc_comn/src/server/opc_server_base.rs (1 hunks)
  • opc_comn/src/server/traits/mod.rs (1 hunks)
  • opc_comn/src/server/traits/opc_common.rs (1 hunks)
  • opc_comn/src/server/traits/opc_shutdown.rs (1 hunks)
  • opc_da/src/opc_browse.rs (1 hunks)
  • opc_da/src/opc_group.rs (1 hunks)
  • opc_da/src/opc_item.rs (1 hunks)
  • opc_da/src/opc_server.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • opc_comn/src/server/traits/opc_shutdown.rs
  • opc_comn/src/client/mod.rs
  • opc_comn/src/lib.rs
  • opc_comn/src/server/traits/mod.rs
  • opc_comn/src/server/traits/opc_common.rs
  • opc_classic_utils/examples/convenience_functions.rs
  • opc_comn/src/server/mod.rs
  • opc_classic_utils/src/memory/tests.rs
  • opc_da/src/opc_server.rs
  • opc_comn/src/server/opc_server_base.rs
  • opc_da/src/opc_group.rs
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
opc_da/src/opc_browse.rs (8)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

opc_classic_utils/src/memory/wstring.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

opc_da/src/opc_item.rs (12)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyzing
🔇 Additional comments (8)
opc_classic_utils/src/memory/wstring.rs (6)

41-46: Good addition for PWSTR support.

The new from_pwstr method provides symmetry with from_pcwstr and enables direct construction from mutable wide string pointers.


139-142: LGTM! Consistent API additions.

Adding as_pwstr() methods to both string types provides a consistent API for Windows interop.


148-149: Good documentation update for Drop behavior.

The comment clearly explains that the pointer is kept intact for the callee, which aligns with the caller-allocated memory ownership model.


224-228: Useful convenience method.

The from_string method provides a convenient way to create CalleeAllocatedWString from owned strings.


291-294: Consistent API addition.

Adding as_pwstr() to CalleeAllocatedWString maintains API consistency between both string types.


348-404: Well-designed macro with comprehensive documentation.

The alloc_callee_wstring! macro provides a clean API for creating COM-allocated wide strings. The documentation thoroughly covers usage, memory management responsibilities, and typical use cases.

opc_da/src/opc_item.rs (1)

203-247: Well-designed trait interface.

The ItemDefinition struct and OPCItem trait provide a clean abstraction for OPC item management with proper error handling through Result types.

opc_da/src/opc_browse.rs (1)

111-129: Clean trait design.

The OPCBrowse trait provides a well-structured interface for OPC browsing functionality with appropriate error handling.

Comment on lines +117 to +127
fn browse(
&self,
item_id: String,
max_elements_returned: u32,
browse_filter: tagOPCBROWSEFILTER,
element_name_filter: String,
vendor_filter: String,
return_all_properties: bool,
return_property_values: bool,
property_ids: Vec<u32>,
) -> Result<(bool, u32, Vec<tagOPCBROWSEELEMENT>)>;

Check warning

Code scanning / clippy

this function has too many arguments (9/7) Warning

this function has too many arguments (9/7)
Comment on lines +129 to +140
fn on_data_change(
&self,
transaction_id: u32,
group: u32,
quality: HRESULT,
error: HRESULT,
client_items: Vec<u32>,
values: Vec<windows::Win32::System::Variant::VARIANT>,
qualities: Vec<u16>,
timestamps: Vec<windows::Win32::Foundation::FILETIME>,
errors: Vec<HRESULT>,
) -> Result<()>;

Check warning

Code scanning / clippy

this function has too many arguments (10/7) Warning

this function has too many arguments (10/7)
Comment on lines +142 to +153
fn on_read_complete(
&self,
transaction_id: u32,
group: u32,
quality: HRESULT,
error: HRESULT,
client_items: Vec<u32>,
values: Vec<windows::Win32::System::Variant::VARIANT>,
qualities: Vec<u16>,
timestamps: Vec<windows::Win32::Foundation::FILETIME>,
errors: Vec<HRESULT>,
) -> Result<()>;

Check warning

Code scanning / clippy

this function has too many arguments (10/7) Warning

this function has too many arguments (10/7)
impl<T: traits::OPCGroup + traits::OPCGroupStateMgt2> IOPCGroupStateMgt_Impl for OPCGroup_Impl<T> {
fn GetState(
&self,
pupdateRate: *mut u32,

Check warning

Code scanning / clippy

variable pupdateRate should have a snake case name Warning

variable pupdateRate should have a snake case name
Comment on lines +188 to +197
fn set_state2(
&self,
requested_update_rate: Option<u32>,
active: Option<bool>,
time_bias: Option<i32>,
percent_deadband: Option<f32>,
locale_id: Option<u32>,
client_group: Option<u32>,
keep_alive: Option<u32>,
) -> Result<(u32, bool, i32, f32, u32, u32, u32)>;

Check warning

Code scanning / clippy

this function has too many arguments (8/7) Warning

this function has too many arguments (8/7)
dwlcid: u32,
phservergroup: *mut u32,
previsedupdaterate: *mut u32,
riid: *const GUID,

Check warning

Code scanning / clippy

unused variable: riid Warning

unused variable: riid
Comment on lines +98 to +107
fn add_group(
&self,
name: String,
active: bool,
requested_update_rate: u32,
client_group: u32,
time_bias: Option<i32>,
percent_deadband: Option<f32>,
locale_id: u32,
) -> Result<(u32, u32, IUnknown)>;

Check warning

Code scanning / clippy

this function has too many arguments (8/7) Warning

this function has too many arguments (8/7)
@@ -0,0 +1,230 @@
use opc_classic_utils::*;

Check warning

Code scanning / clippy

unused import: opc_classic_utils::* Warning

unused import: opc_classic_utils::*
Comment on lines +217 to +222
) -> Result<(
Vec<windows::Win32::System::Variant::VARIANT>,
Vec<u16>,
Vec<windows::Win32::Foundation::FILETIME>,
Vec<HRESULT>,
)>;

Check warning

Code scanning / clippy

very complex type used. Consider factoring parts into type definitions Warning

very complex type used. Consider factoring parts into type definitions
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

♻️ Duplicate comments (1)
opc_da/src/opc_item.rs (1)

23-32: String conversion inconsistency between AddItems and ValidateItems.

This issue was previously identified. AddItems uses from_pwstr without null checks while ValidateItems uses from_pcwstr with explicit null checks. This inconsistency could lead to crashes if null pointers are passed.

Apply the same null-checking pattern used in ValidateItems:

                 access_path: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szAccessPath)
-                        .to_string_lossy()
-                        .unwrap_or_default()
+                    if item.szAccessPath.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szAccessPath.0))
+                            .to_string_lossy()
+                            .unwrap_or_default()
+                    }
                 },
                 item_id: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szItemID)
-                        .to_string_lossy()
-                        .unwrap_or_default()
+                    if item.szItemID.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szItemID.0))
+                            .to_string_lossy()
+                            .unwrap_or_default()
+                    }
                 },
🧹 Nitpick comments (1)
opc_da/src/opc_server.rs (1)

10-58: Consider extracting repeated string conversion pattern.

The string conversion pattern from PCWSTR is repeated in both AddGroup and GetGroupByName. Consider extracting it into a helper function to reduce duplication and improve maintainability.

fn pcwstr_to_string(pcwstr: PCWSTR) -> Result<String> {
    unsafe {
        CallerAllocatedWString::from_pcwstr(pcwstr)
            .to_string_lossy()
            .ok_or(windows::Win32::Foundation::E_POINTER)
    }
}

Then use it as:

-        let name = unsafe {
-            CallerAllocatedWString::from_pcwstr(*szname)
-                .to_string_lossy()
-                .ok_or(windows::Win32::Foundation::E_POINTER)
-        }?;
+        let name = pcwstr_to_string(*szname)?;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bd4bdbd and 0496fcd.

📒 Files selected for processing (4)
  • opc_classic_utils/src/memory/array.rs (2 hunks)
  • opc_da/src/opc_item.rs (1 hunks)
  • opc_da/src/opc_server.rs (1 hunks)
  • opc_da/src/opc_sync_io.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • opc_da/src/opc_sync_io.rs
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
opc_classic_utils/src/memory/array.rs (6)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_properties.rs:58-62
Timestamp: 2024-12-11T01:41:07.039Z
Learning: When count is of type u32, it's safe to set array lengths without additional bounds checking.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

opc_da/src/opc_item.rs (12)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_da/src/opc_server.rs (7)

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:8-35
Timestamp: 2024-12-11T01:49:14.219Z
Learning: In the file opc_da/src/client/tests.rs, do not add retry logic in test functions as per the user's preference.

🧬 Code Graph Analysis (2)
opc_classic_utils/src/memory/array.rs (2)
opc_classic_utils/src/memory/ptr_array.rs (13)
  • from_slice (255-274)
  • as_ptr (80-82)
  • as_ptr (305-307)
  • allocate (37-59)
  • allocate (280-302)
  • len (96-98)
  • len (321-323)
  • is_null (106-108)
  • is_null (331-333)
  • new (24-26)
  • new (243-245)
  • as_mut_slice (128-134)
  • as_mut_slice (353-359)
opc_classic_utils/src/memory/ptr.rs (7)
  • as_ptr (58-60)
  • as_ptr (175-177)
  • allocate (35-41)
  • is_null (72-74)
  • is_null (189-191)
  • new (22-24)
  • new (148-150)
opc_da/src/opc_server.rs (3)
opc_classic_utils/src/memory/wstring.rs (2)
  • from_pcwstr (35-39)
  • from_pcwstr (220-224)
opc_comn/src/server/opc_server_base.rs (1)
  • GetErrorString (29-34)
opc_classic_utils/src/memory/ptr.rs (2)
  • from_value (46-55)
  • from_value (162-172)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyzing
🔇 Additional comments (3)
opc_classic_utils/src/memory/array.rs (1)

162-169: Potential use-after-drop safety issue in Drop implementation.

While the comment indicates that the pointer should be preserved for the callee, only setting self.len = 0 doesn't prevent the struct from being used after drop. The pointer remains valid in the struct, which could lead to accidental use-after-drop.

Consider adding debug assertions or explicit documentation warning about this behavior:

 impl<T> Drop for CallerAllocatedArray<T> {
     fn drop(&mut self) {
         // Do NOT free the memory - the callee is responsible for this
         // Just clear the length to prevent use-after-free
         // Keep the pointer intact for the callee to use
         self.len = 0;
         // Note: We don't clear self.ptr because the callee needs it
+        
+        // SAFETY: After drop, this struct should not be used. The pointer
+        // is intentionally preserved for the callee to access and free.
+        #[cfg(debug_assertions)]
+        {
+            // In debug builds, we could track dropped instances
+        }
     }
 }
⛔ Skipped due to learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling `std::mem::forget` on a value that implements `Copy`, such as `PWSTR`, has no effect because the value is copied and the original is still dropped. Therefore, using `std::mem::forget` in such cases does not prevent the value from being dropped or prevent a potential double-free.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
opc_da/src/opc_server.rs (1)

60-91: Well-implemented COM interface methods.

The implementation correctly:

  • Uses alloc_callee_wstring! for returning COM-allocated strings
  • Properly transfers ownership with into_raw() for allocated pointers
  • Handles COM interface wrapping appropriately
opc_da/src/opc_item.rs (1)

195-239: Well-designed trait interface for OPC item management.

The ItemDefinition struct and OPCItem trait provide a clean abstraction over the COM interface, properly separating concerns and making the API more Rust-idiomatic.

Comment on lines +413 to +480
/// Copies data directly to a caller-allocated array
///
/// This macro simplifies copying data directly to a caller-allocated array
/// without allocating new memory. It's used when the caller has already
/// allocated the array and we just need to copy data into it.
///
/// # Arguments
///
/// * `$dst` - A raw pointer (`*mut T`) to the destination array
/// * `$src` - A slice (`&[T]`) containing the source data
///
/// # Returns
///
/// Returns `Result<(), windows::core::Error>`:
/// * `Ok(())` - Data was successfully copied
/// * `Err(E_INVALIDARG)` - The destination pointer is null
///
/// # Safety
///
/// The caller must ensure that:
/// * `$dst` is a valid pointer to caller-allocated memory
/// * The destination array has sufficient space for the source data
/// * The array elements are `Copy` types
///
/// # Example
///
/// ```rust
/// use opc_classic_utils::copy_to_caller_array;
///
/// let mut array: [u32; 5] = [0; 5];
/// let data = vec![1u32, 2u32, 3u32, 4u32, 5u32];
///
/// // Copy data directly to the caller-allocated array
/// copy_to_caller_array!(array.as_mut_ptr(), &data)?;
/// assert_eq!(array, [1, 2, 3, 4, 5]);
/// # Ok::<(), windows::core::Error>(())
/// ```
///
/// # Typical Use Cases
///
/// * Copying data to caller-allocated output parameters
/// * OPC Classic API array output parameters
/// * Direct memory copying without allocation
#[macro_export]
macro_rules! copy_to_caller_array {
($dst:expr, $src:expr) => {
unsafe {
let src = $src;
let mut dst_ptr = opc_classic_utils::CallerAllocatedArray::allocate(src.len())?;
if dst_ptr.is_null() {
return Err(windows::core::Error::new(
windows::Win32::Foundation::E_INVALIDARG,
"Destination pointer is null",
));
}
*$dst = dst_ptr.as_ptr();
if let Some(dst_slice) = dst_ptr.as_mut_slice() {
dst_slice.copy_from_slice(src);
Ok(())
} else {
Err(windows::core::Error::new(
windows::Win32::Foundation::E_INVALIDARG,
"Failed to get mutable slice",
))
}
}
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Misleading macro name and implementation for copy_to_caller_array!

The macro name and documentation claim it "copies data directly to a caller-allocated array without allocating new memory", but the implementation actually allocates new memory using CallerAllocatedArray::allocate() at line 461.

This is fundamentally incorrect and misleading. The macro should either:

  1. Be renamed to reflect that it allocates memory (e.g., alloc_and_copy_to_caller_array!)
  2. Be rewritten to actually copy to existing memory without allocation

The current implementation could lead to serious bugs where developers expect to copy to existing memory but instead allocate new memory, potentially causing memory leaks.

If the intent is to allocate memory, consider this corrected implementation with a more accurate name:

-/// Copies data directly to a caller-allocated array
+/// Allocates memory and copies data to a caller-allocated array pointer
 ///
-/// This macro simplifies copying data directly to a caller-allocated array
-/// without allocating new memory. It's used when the caller has already
-/// allocated the array and we just need to copy data into it.
+/// This macro allocates new memory using CoTaskMemAlloc and copies data
+/// into it, then writes the pointer to the caller-provided location.
 ///
 #[macro_export]
-macro_rules! copy_to_caller_array {
+macro_rules! alloc_and_copy_to_caller_array {
     ($dst:expr, $src:expr) => {
         unsafe {
             let src = $src;
-            let mut dst_ptr = opc_classic_utils::CallerAllocatedArray::allocate(src.len())?;
-            if dst_ptr.is_null() {
-                return Err(windows::core::Error::new(
-                    windows::Win32::Foundation::E_INVALIDARG,
-                    "Destination pointer is null",
-                ));
-            }
-            *$dst = dst_ptr.as_ptr();
-            if let Some(dst_slice) = dst_ptr.as_mut_slice() {
+            let mut array = opc_classic_utils::CallerAllocatedArray::allocate(src.len())?;
+            if let Some(dst_slice) = array.as_mut_slice() {
                 dst_slice.copy_from_slice(src);
-                Ok(())
-            } else {
-                Err(windows::core::Error::new(
-                    windows::Win32::Foundation::E_INVALIDARG,
-                    "Failed to get mutable slice",
-                ))
             }
+            *$dst = array.into_raw().0;
+            Ok(())
         }
     };
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In opc_classic_utils/src/memory/array.rs between lines 413 and 480, the macro
copy_to_caller_array! is misleading because it claims to copy data to a
caller-allocated array without allocation, but it actually allocates new memory
using CallerAllocatedArray::allocate(). To fix this, either rename the macro to
reflect that it allocates memory (e.g., alloc_and_copy_to_caller_array!) or
rewrite the macro to copy data directly into the provided destination pointer
without any allocation. Ensure the implementation matches the macro name and
documentation to avoid confusion and potential memory issues.

Comment on lines 50 to 53
copy_to_caller_array!(ppaddresults, &results);

// Allocate and write errors
copy_to_caller_array!(pperrors, &errors)?;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Using misleadingly named copy_to_caller_array! macro.

The copy_to_caller_array! macro name suggests it copies to existing memory, but it actually allocates new memory. While this behavior might be correct for COM output parameters (where the callee allocates), the macro name is misleading and could cause confusion.

Consider using a more accurately named macro or the direct approach:

-        copy_to_caller_array!(ppaddresults, &results);
+        // Allocate and write results
+        let array = CallerAllocatedArray::from_slice(&results)?;
+        *ppaddresults = array.into_raw().0;

Or wait for the macro to be renamed to something like alloc_and_copy_to_caller_array! to better reflect its behavior.

Also applies to: 106-109, 125-125, 144-144, 164-164, 185-185

🤖 Prompt for AI Agents
In opc_da/src/opc_item.rs around lines 50 to 53, the macro copy_to_caller_array!
is misleadingly named because it allocates new memory instead of copying to
existing memory. To fix this, rename the macro to something more descriptive
like alloc_and_copy_to_caller_array! to clearly indicate that it performs
allocation and copying. Alternatively, replace the macro usage with explicit
allocation and copy code for clarity. Apply the same renaming or replacement to
the other occurrences at lines 106-109, 125, 144, 164, and 185.

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 (4)
opc_classic_utils/src/memory/array.rs (1)

409-476: Critical: Misleading macro name and implementation for copy_to_caller_array!

The macro name and documentation claim it "copies data directly to a caller-allocated array without allocating new memory", but the implementation actually allocates new memory using CallerAllocatedArray::allocate() at line 457.

This is fundamentally incorrect and misleading. The macro should either:

  1. Be renamed to reflect that it allocates memory (e.g., alloc_and_copy_to_caller_array!)
  2. Be rewritten to actually copy to existing memory without allocation

The current implementation could lead to serious bugs where developers expect to copy to existing memory but instead allocate new memory, potentially causing memory leaks.

opc_da/src/opc_item.rs (2)

23-32: Inconsistent string conversion methods between AddItems and ValidateItems.

AddItems uses from_pwstr while ValidateItems uses from_pcwstr with explicit null checks. This inconsistency could lead to maintenance issues.

Standardize the approach across both methods. Since ValidateItems has proper null checks, apply the same pattern to AddItems:

                 access_path: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szAccessPath)
-                        .to_string_lossy()
-                        .unwrap_or_default()
+                    if item.szAccessPath.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szAccessPath.0))
+                            .to_string_lossy()
+                            .unwrap_or_default()
+                    }
                 },
                 item_id: unsafe {
-                    CallerAllocatedWString::from_pwstr(item.szItemID)
-                        .to_string_lossy()
-                        .unwrap_or_default()
+                    if item.szItemID.is_null() {
+                        String::new()
+                    } else {
+                        CallerAllocatedWString::from_pcwstr(PCWSTR(item.szItemID.0))
+                            .to_string_lossy()
+                            .unwrap_or_default()
+                    }
                 },

Also applies to: 71-88


50-53: Using misleadingly named copy_to_caller_array! macro.

The copy_to_caller_array! macro name suggests it copies to existing memory, but it actually allocates new memory. While this behavior might be correct for COM output parameters (where the callee allocates), the macro name is misleading and could cause confusion.

Consider using a more accurately named macro or the direct approach:

-        copy_to_caller_array!(ppaddresults, &results)?;
+        // Allocate and write results
+        let array = CallerAllocatedArray::from_slice(&results)?;
+        *ppaddresults = array.into_raw().0;

Or wait for the macro to be renamed to something like alloc_and_copy_to_caller_array! to better reflect its behavior.

Also applies to: 106-109, 125-125, 144-144, 164-164, 185-185

opc_classic_utils/src/memory/wstring.rs (1)

326-344: Fix buffer overflow in memory allocation.

The memory allocation on line 335 doesn't account for the null terminator, causing a buffer overflow when copying the entire wide_string (which includes the null terminator).

Apply this fix:

-        let ptr = unsafe { CoTaskMemAlloc(len * std::mem::size_of::<u16>()) };
+        let ptr = unsafe { CoTaskMemAlloc((len + 1) * std::mem::size_of::<u16>()) };

The allocation should include space for the null terminator since wide_string.len() includes it but len doesn't.

🧹 Nitpick comments (1)
opc_classic_utils/src/memory/wstring.rs (1)

346-402: Well-designed macro with dependency on FromStr fix.

The alloc_callee_wstring! macro provides excellent abstraction for wide string allocation with comprehensive documentation. However, it depends on the FromStr implementation which has a buffer overflow issue that needs to be fixed first.

Once the buffer overflow in FromStr is fixed, this macro will provide a safe and ergonomic way to allocate wide strings for COM interop.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0496fcd and 2c758e1.

📒 Files selected for processing (6)
  • opc_classic_utils/src/memory/array.rs (4 hunks)
  • opc_classic_utils/src/memory/ptr.rs (5 hunks)
  • opc_classic_utils/src/memory/ptr_array.rs (4 hunks)
  • opc_classic_utils/src/memory/wstring.rs (10 hunks)
  • opc_da/src/opc_data_callback.rs (1 hunks)
  • opc_da/src/opc_item.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • opc_da/src/opc_data_callback.rs
  • opc_classic_utils/src/memory/ptr_array.rs
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like `copy_to_pointer` are unnecessary and should be omitted.
Learnt from: Ronbb
PR: Ronbb/rust_opc#10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In `opc_da/src/client/iterator.rs`, when initializing the `cache` array in `GroupIterator`, use `cache: [const { None }; 16]` instead of `cache: [None; 16]` because Flipper requires this change.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the `Drop` trait via `IUnknown`, automatically releasing resources when they go out of scope, so manual implementation of `Drop` for structs holding COM interfaces is unnecessary.
Learnt from: Ronbb
PR: Ronbb/rust_opc#9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In `windows-rs`, COM interfaces implement `IUnknown`, which automatically calls `Release` when dropped. Therefore, it's unnecessary to manually implement the `Drop` trait to release COM interfaces in structs that hold COM interfaces.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the `opc_da/src/com/server.rs` file (Rust), the enum variant `OPC_NS_HIERARCHIAL` is correctly spelled as per the OPC specification, even though it appears misspelled.
Learnt from: Ronbb
PR: Ronbb/rust_opc#8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In `opc_da/src/traits/group.rs`, the `GroupTrait` trait contains only method declarations without implementations.
opc_classic_utils/src/memory/ptr.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

opc_da/src/opc_item.rs (11)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_mgt.rs:191-194
Timestamp: 2024-12-10T03:00:26.689Z
Learning: In opc_da/src/client/traits/item_mgt.rs, the CreateEnumerator method returns a Result, so there is no need to handle None values.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/unified/group.rs:28-39
Timestamp: 2024-12-10T13:51:14.061Z
Learning: In the add_items method in opc_da/src/client/unified/group.rs, the OPC DA protocol guarantees that the results and errors arrays returned will always be of the same length, so additional validation for array length mismatch is not necessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/server.rs:714-716
Timestamp: 2024-11-29T12:06:30.463Z
Learning: In the opc_da/src/com/server.rs file (Rust), the enum variant OPC_NS_HIERARCHIAL is correctly spelled as per the OPC specification, even though it appears misspelled.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #8
File: opc_da/src/traits/group.rs:1-122
Timestamp: 2024-12-02T14:43:35.670Z
Learning: In opc_da/src/traits/group.rs, the GroupTrait trait contains only method declarations without implementations.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/async_io2.rs:94-95
Timestamp: 2024-12-11T01:29:33.264Z
Learning: In AsyncIo2Trait within opc_da/src/client/traits/async_io2.rs, when directly returning a Result from a method like Refresh2, it's acceptable to return the result as is without additional wrapping.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/tests.rs:0-0
Timestamp: 2024-12-11T01:25:38.423Z
Learning: In Rust OPC DA client tests, resources like groups do not require explicit cleanup as they will be released after the OPC DA server disconnects.

opc_classic_utils/src/memory/array.rs (6)

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/traits/item_properties.rs:58-62
Timestamp: 2024-12-11T01:41:07.039Z
Learning: When count is of type u32, it's safe to set array lengths without additional bounds checking.

Learnt from: Ronbb
PR: #10
File: opc_da/src/client/iterator.rs:114-114
Timestamp: 2024-12-13T18:47:42.521Z
Learning: In opc_da/src/client/iterator.rs, when initializing the cache array in GroupIterator, use cache: [const { None }; 16] instead of cache: [None; 16] because Flipper requires this change.

opc_classic_utils/src/memory/wstring.rs (4)

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/memory.rs:96-106
Timestamp: 2024-12-09T15:29:03.494Z
Learning: In Rust, calling std::mem::forget on a value that implements Copy, such as PWSTR, has no effect because the value is copied and the original is still dropped. Therefore, using std::mem::forget in such cases does not prevent the value from being dropped or prevent a potential double-free.

Learnt from: Ronbb
PR: #8
File: opc_da/src/com/utils.rs:61-87
Timestamp: 2024-11-28T15:39:26.903Z
Learning: In this codebase, all data copying functions should use COM. Non-COM functions like copy_to_pointer are unnecessary and should be omitted.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:67-78
Timestamp: 2024-12-10T14:06:24.207Z
Learning: In windows-rs, COM interfaces implement the Drop trait via IUnknown, automatically releasing resources when they go out of scope, so manual implementation of Drop for structs holding COM interfaces is unnecessary.

Learnt from: Ronbb
PR: #9
File: opc_da/src/client/v3/mod.rs:9-15
Timestamp: 2024-12-10T14:03:29.606Z
Learning: In windows-rs, COM interfaces implement IUnknown, which automatically calls Release when dropped. Therefore, it's unnecessary to manually implement the Drop trait to release COM interfaces in structs that hold COM interfaces.

🧬 Code Graph Analysis (3)
opc_classic_utils/src/memory/ptr.rs (4)
opc_classic_utils/src/memory/array.rs (4)
  • into_raw (86-90)
  • into_raw (251-255)
  • from_raw (27-29)
  • from_raw (239-241)
opc_classic_utils/src/memory/ptr_array.rs (4)
  • into_raw (87-91)
  • into_raw (308-312)
  • from_raw (29-31)
  • from_raw (246-248)
opc_classic_utils/src/memory/wstring.rs (4)
  • into_raw (117-119)
  • into_raw (275-277)
  • from_raw (30-32)
  • from_raw (213-215)
opc_classic_utils/examples/memory_management_comparison.rs (4)
  • ptr (36-36)
  • ptr (57-57)
  • ptr (83-83)
  • ptr (90-90)
opc_classic_utils/src/memory/array.rs (2)
opc_classic_utils/src/memory/ptr.rs (9)
  • into_raw (65-67)
  • into_raw (180-182)
  • as_ptr (58-60)
  • as_ptr (173-175)
  • allocate (35-41)
  • is_null (70-72)
  • is_null (185-187)
  • new (22-24)
  • new (146-148)
opc_classic_utils/src/memory/ptr_array.rs (15)
  • into_raw (87-91)
  • into_raw (308-312)
  • from_slice (251-270)
  • as_ptr (80-82)
  • as_ptr (301-303)
  • allocate (37-59)
  • allocate (276-298)
  • len (94-96)
  • len (315-317)
  • is_null (104-106)
  • is_null (325-327)
  • new (24-26)
  • new (239-241)
  • as_mut_slice (126-132)
  • as_mut_slice (347-353)
opc_classic_utils/src/memory/wstring.rs (3)
opc_classic_utils/src/memory/ptr.rs (6)
  • into_raw (65-67)
  • into_raw (180-182)
  • drop (102-106)
  • drop (217-224)
  • new (22-24)
  • new (146-148)
opc_classic_utils/src/memory/array.rs (8)
  • into_raw (86-90)
  • into_raw (251-255)
  • drop (161-167)
  • drop (326-334)
  • new (22-24)
  • new (232-234)
  • len (93-95)
  • len (258-260)
opc_classic_utils/src/memory/ptr_array.rs (8)
  • into_raw (87-91)
  • into_raw (308-312)
  • drop (165-168)
  • drop (386-402)
  • new (24-26)
  • new (239-241)
  • len (94-96)
  • len (315-317)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: analyzing
  • GitHub Check: build
🔇 Additional comments (9)
opc_classic_utils/src/memory/ptr.rs (3)

65-67: Good simplification of ownership transfer semantics.

The change from mut self to self and removal of pointer nullification is correct. Since into_raw consumes the wrapper, there's no risk of use-after-free, and the ownership transfer is clear at the type level.

Also applies to: 180-182


104-105: Clear documentation of memory ownership semantics.

The added comments explicitly document why the pointer is kept intact, which helps prevent confusion about the memory management strategy for caller-allocated pointers.


235-288: Well-designed macro for safe pointer writes.

The macro provides excellent safety abstractions:

  • Proper null pointer validation with E_INVALIDARG
  • Clear ownership semantics using CallerAllocatedPtr
  • Comprehensive documentation with safety requirements
  • Practical example demonstrating usage

The design correctly handles the COM pattern where the caller allocates but doesn't free the memory.

opc_classic_utils/src/memory/array.rs (3)

86-89: Consistent ownership transfer pattern.

The simplified into_raw methods correctly implement move semantics by taking self by value, matching the pattern established across all memory wrapper types in the crate.

Also applies to: 251-254


163-167: Appropriate Drop implementation for caller-allocated arrays.

Setting length to 0 while preserving the pointer is a good defensive practice that prevents use of an invalid length while still allowing the callee to access the allocated memory.


346-407: Well-designed array allocation macro.

The write_caller_allocated_array! macro correctly:

  • Documents that it allocates memory
  • Uses established patterns from other macros
  • Provides clear memory management documentation
  • Includes practical examples
opc_classic_utils/src/memory/wstring.rs (3)

41-46: Good addition of PWSTR support methods.

The new from_pwstr and as_pwstr methods provide consistent mutable pointer support, complementing the existing PCWSTR methods with the same safety model.

Also applies to: 131-134, 289-292


140-141: Consistent Drop implementation documentation.

The added comments align with the pattern established across all memory wrapper types, clearly documenting the ownership semantics.


183-189: Useful conversion trait implementation.

The TryFrom<PCWSTR> implementation provides ergonomic conversions while maintaining API consistency by returning a Result, even though it always succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants