Skip to content

Coin 5 design: model full-path access as a real view #741

Description

@Dikluwe

Motivation

PR #714 fixes undefined behavior caused by treating arbitrary SoPath objects as SoFullPath objects. It adds explicit full-path accessors to SoPath and migrates Coin's internal fake downcasts, while retaining SoFullPath for compatibility.

This issue is intentionally about a future ABI/API-breaking Coin 5 design, not a prerequisite for #714.

Historical model

SoFullPath comes from the Open Inventor API. It is declared as a public subclass of SoPath, adds no data members, has a private constructor/destructor, and exposes alternate non-virtual accessors that include hidden children. Coin's initial 1999 revision documented that an arbitrary SoPath could be cast to SoFullPath because the derived class had no additional data.

That made SoFullPath act as an "extended interface" or view selected by changing the static pointer type:

SoPath * path = ...;
SoNode * tail = static_cast<SoFullPath *>(path)->getTail();

However, the November 1997 C++ working paper already specified in [expr.static.cast] that a base-to-derived pointer cast is only valid when the base pointer designates a base subobject of an actual derived object. Otherwise the result of the cast is undefined. C++98 and every later standard retain this object-identity requirement. Equal layout and the absence of additional data members do not create a derived object.

The inheritance itself has one valid use: SoTempPath genuinely derives from SoFullPath. The invalid part is using inheritance as a universal view over plain SoPath instances.

Why discuss this for Coin 5

Coin has explicitly required at least C++11 since version 4.0.3. PR #714 reconciles the current implementation with that language model without breaking API or ABI, but leaves the historical compatibility facade in place.

A major release gives us an opportunity to represent the original "extended interface" intention directly and safely.

Possible Coin 5 model

A modern view would use composition rather than a downcast:

class SoFullPathView final {
public:
  int getLength() const noexcept;
  SoNode * getTail() const noexcept;
  SoNode * getNodeFromTail(int index) const;
  int getIndexFromTail(int index) const;

  SoPath & path() const noexcept;

private:
  friend class SoPath;
  explicit SoFullPathView(SoPath & path) noexcept : path_(&path) {}
  SoPath * path_;
};

class SoPath {
public:
  SoFullPathView fullView() noexcept;
};

Usage becomes:

SoPath * path = ...;
auto full = path->fullView();
SoNode * tail = full.getTail();

The view can be one pointer wide, require no allocation, and inline to the same underlying accesses. A const view and an explicit lifetime policy would be needed. We should decide whether the public view is borrowed, retains its SoPath through SoRef, or whether both forms are useful.

Relationship to Open Inventor compatibility

There are two different compatibility goals:

  1. Literal C++ API compatibility. Open Inventor defines SoFullPath : public SoPath. Code may pass SoFullPath* where SoPath* is expected and may use all inherited SoPath operations. A composition-based view would break this source-level relationship as well as ABI.
  2. Architectural intent. Open Inventor and Coin describe SoFullPath as an extended interface over storage already owned by SoPath. A view represents that intent more accurately than a false derived-class cast.

There is also a useful precedent in current Open Inventor managed bindings: they expose a SoPath.FullPath property and document usage such as path.FullPath.GetTail(), rather than asking managed-language users to perform the C++ downcast.

Design alternatives

A. Add SoFullPathView and retain legacy SoFullPath temporarily

  • Best migration path and clearest naming.
  • Preserves a compatibility window.
  • Leaves two full-path interfaces during the transition.

B. Redefine SoFullPath itself as the view in Coin 5

  • Makes the historic name finally match its documented "extended interface" purpose.
  • Breaks Open Inventor C++ source compatibility, not only ABI.
  • Requires changing SoTempPath to derive directly from SoPath.
  • Requires migration for code relying on implicit SoFullPath* to SoPath* conversion or inherited methods.

C. Keep the inheritance permanently, but retire its use as a view

  • Keep SoFullPath only for genuine derived objects and compatibility.
  • Make SoPath::getFull*() the normal API.
  • Smallest ecosystem disruption, but preserves a confusing historical type.

Questions for the Coin 5 design

  • Is literal Open Inventor C++ source compatibility still a Coin 5 requirement?
  • Should the modern type be named SoFullPathView, or should Coin 5 redefine SoFullPath?
  • Should a view be borrowed or retain the underlying SoPath?
  • Should SoNodeKitPath eventually become another view over the same storage?
  • How many release cycles should the legacy class/accessors remain deprecated before removal?
  • Should SoTempPath derive directly from SoPath in Coin 5?

Suggested migration sequence

  1. Merge Fix SoPath/SoFullPath downcast undefined behavior across the codebase #714: expose SoPath::getFull*() and remove Coin's invalid internal downcasts.
  2. Document arbitrary SoPath* to SoFullPath* casts as unsupported and provide a downstream migration table.
  3. Introduce and test a view API during the Coin 4 series if maintainers want implementation experience before Coin 5.
  4. In Coin 5, change SoTempPath's base if necessary and choose between alternatives A, B, and C above.

References

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions