Abstract: An interactive report to show all the references that depend on C++ friend statements.
C++ classes can have friends. These friends are allowed to access private and protected class members. Because this access breaks the data encapsulation paradigm, friends are generally discouraged. But, suppose the friendship already exists? What would it take to sever ties?
You can download the functional Interactive Report here.
Find My Friends
Let’s use Understand to answer this question. We want to find all of the references that depend on the friend relationship. Let’s assume we’re using the Python API and that we already have the class entity with a friend relationship in a variable called “ent.” We’ll talk about how to get the class entity last.
The first thing we want to find is the list of friends. We access friends by looking for “friend” references, like this:
ent.refs("c friend")
It’s possible to have multiple friends, and for each friend we’ll want to store more information. So, let’s store our friends as dictionary keys. We’ll use the entity rather than the reference as the key because we’ll be comparing entities later. We’ll store the reference in a dictionary for later too.
friends = {}
frefs = {}
for ref in ent.refs("c friend"):
friends[ref.ent()] = {}
frefs[ref.ent()] = ref
Finding Dependent References
Our friends have access to the private and protected members. So, let’s start with that list of members. In this case, we only care about each member once, so we use unique=True to have, at most, one reference to each member.
members = {}
for ref in ent.refs("define,declare","protected member, private member", True):
member = ref.ent()
Now we need to look at each reference for this member to see if it depends on a friendship. Suppose class B is my friend, and the B::foo() function calls the protected function bar. The reference I’m looking at would be something like this:
- ent: B::foo()
- scope: A::bar()
- kind: callby
- file, line, and column
We need some way to know that B::foo() is part of our friend class B and not part of our current class A. We can find the class by following the parent chain of B::foo(). If the referenced entity or any of its parents is one of our friends, then the reference depends on that friend specifier.
We can also make one other assumption: only reverse references are potential friend references. In the sample, bar being called by foo is a potential friend reference because bar is the entity being called. In the reverse case, if A::bar calls B::foo, then that reference is going out of class A and therefore doesn’t depend on A’s friends.
Finally, we’ll store these references in two different two-layer dictionaries. The first dictionary will go from friend to member to list of references. The second goes from member to friend to list of references.
# Look for references that belong to a friend's scope
friendrefs = []
for ref in member.refs():
# only worry about reverse refs (setby, useby, callby, etc)
if ref.isforward():
continue
par = ref.ent()
while par:
if par in friends:
friends[par].setdefault(member, []).append(ref)
members[member].setdefault(par,[]).append(ref)
par = par.parent()
Interactive Reports
With the code snippets above, we can generate the information we want, but it’s not very accessible right now. We haven’t decided how to get the class entity to start with or how to display the information.
Let’s use an interactive report. Interactive reports are plugins for Understand. They can be run on entities, architectures, or the entire database. The report output is displayed in Understand and can sync to other Understand views.
To make our script into an interactive report, we have to define two required functions. The first one is the name that will be displayed in the UI. I’ll name the report “Friend References.”
def name():
"""
Required, the name of the ireport.
"""
return "Friend References"
The second function is the generate function. The generate function has two parameters: a report object and the target. The report object is used to send output to Understand to display. The target can be an entity, architecture, or the entire database. The top of the generate function will be the code we’ve written so far to find and group the references.
def generate(report, ent):
"""
Required, generate the report
"""
We need to define one more thing for a working ireport. The test_entity, test_architecture, and test_global functions are used to determine if an entity, architecture, and database target are valid. If these functions are not defined, they are assumed false. So, right now, our interactive report will never be valid and thus never be visible. We’ll define the test_entity function to enable this report for any C++ class that has at least one friend reference.
def test_entity(ent):
"""
Optional method, return true if report is valid for the entity
"""
# This is valid for c class's that have a friend reference
return ent.kind().check("c class, c struct") and len(ent.refs("c friend")) > 0
At this point, our script will be visible. But it doesn’t output anything. We also need the code in the generate function that prints output to Understand. You can refer to the full plugin to see that code.
The Results
To access this report from Understand, it needs to be able to find the plugin. Understand looks for plugins by searching for files with a .upl (Perl plugin) or .upy (Python plugin) extension in the application data directory. See this article for platform-specific list of locations. Or, drag the plugin file onto the Understand GUI and click install on the prompt to have Understand copy the file to the correct location for you.
Now, the report will be visible in the right-click menu of applicable entities. I’ll use the git::Blame class in the GitAhead sample project:
The output is shown in the a dock window:
Blame declares Repository as a friend on line 56. Clicking that will open that location in the editor. The only reference that depends on that friend statement is a call from Repository::blame() to the protected Blame::Blame constructor on line 844. Entities are shown in blue and can be interacted with just like entities shown in other Understand windows.
As a more complex example, we can try the report on the git::Repository class.
Interestingly, the Branch class doesn’t have any dependent references. So, Branch doesn’t have to be Repository’s friend, and that line of code could be removed. From the Group By Member tree, we can also tell that all the friend references go to only two of the potential protected and private members. If those two members were public, none of the friend references would be necessary.
Interested in more interactive reports? This github repository has several you can try, and this article describes some maintainability reports. Or, write your own!