Skip to content

Commit

Permalink
Added Objective C nested struct and enum test case (#296)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkryza committed Sep 30, 2024
1 parent 781c844 commit d8df7dc
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
make check-formatting
- name: Build and unit test
run: |
NUMPROC=2 CODE_COVERAGE=ON LLVM_VERSION=15 make test
NUMPROC=2 CODE_COVERAGE=ON LLVM_VERSION=15 ENABLE_OBJECTIVE_C_TEST_CASES=OFF make test
- name: Run coverage
run: |
lcov -c -d debug -o coverage.info
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: brew install llvm@18 ninja yaml-cpp cmake ccache

- name: Build and test
run: CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ CMAKE_PREFIX=/opt/homebrew/opt/llvm/lib/cmake/llvm/ CMAKE_GENERATOR=Ninja CMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/opt/llvm/lib/c++ -Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++" make test
run: CC=/opt/homebrew/opt/llvm/bin/clang CXX=/opt/homebrew/opt/llvm/bin/clang++ CMAKE_PREFIX=/opt/homebrew/opt/llvm/lib/cmake/llvm/ CMAKE_GENERATOR=Ninja CMAKE_EXE_LINKER_FLAGS="-L/opt/homebrew/opt/llvm/lib/c++ -Wl,-rpath,/opt/homebrew/opt/llvm/lib/c++" ENABLE_OBJECTIVE_C_TEST_CASES=ON make test

- name: Print build version
run: debug/src/clang-uml --version
Expand Down
3 changes: 1 addition & 2 deletions cmake/FindGNUstep.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ separate_arguments(GNUSTEP_OBJC_FLAGS NATIVE_COMMAND ${GNUSTEP_OBJC_FLAGS_STR})
list(REMOVE_ITEM GNUSTEP_OBJC_FLAGS "-MMD")
list(APPEND GNUSTEP_OBJC_FLAGS "-fblocks")
list(APPEND GNUSTEP_OBJC_FLAGS "-fobjc-runtime=gnustep-2.0")
#set(GNUSTEP_OBJC_FLAGS_STR "${GNUSTEP_OBJC_FLAGS_STR}

# Get GNUstep LDFLAGS (for linking)
execute_process(
Expand All @@ -35,4 +34,4 @@ set(GNUSTEP_LIBRARIES "")
set(GNUSTEP_CFLAGS ${GNUSTEP_OBJC_FLAGS})
set(GNUSTEP_LDFLAGS ${GNUSTEP_LDFLAGS})

message(STATUS "GNUstep detected: CFLAGS: ${GNUSTEP_CFLAGS} LDFLAGS: ${GNUSTEP_LDFLAGS}")
message(STATUS "GNUstep detected")
98 changes: 48 additions & 50 deletions src/class_diagram/visitor/translation_unit_visitor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
}
}

const auto *lexical_parent = enm->getLexicalParent();
if (!parent_id_opt && lexical_parent != nullptr) {
if (const auto *parent_interface_decl =
clang::dyn_cast<clang::ObjCInterfaceDecl>(lexical_parent);
parent_interface_decl != nullptr) {

eid_t ast_id{parent_interface_decl->getID()};

// First check if the parent has been added to the diagram as
// regular class
parent_id_opt = id_mapper().get_global_id(ast_id);
}
}

if (parent_id_opt && diagram().find<class_>(*parent_id_opt)) {
auto parent_class = diagram().find<class_>(*parent_id_opt);

Expand All @@ -157,6 +171,15 @@ bool translation_unit_visitor::VisitEnumDecl(clang::EnumDecl *enm)
e.add_relationship({relationship_t::kContainment, *parent_id_opt});
e.nested(true);
}
else if (parent_id_opt && diagram().find<objc_interface>(*parent_id_opt)) {
auto parent_class = diagram().find<objc_interface>(*parent_id_opt);

e.set_namespace(ns);
e.set_name(parent_class.value().name() + "##" + enm->getNameAsString());
e.set_id(common::to_id(e.full_name(false)));
e.add_relationship({relationship_t::kContainment, *parent_id_opt});
e.nested(true);
}
else {
e.set_name(common::get_tag_name(*enm));
e.set_namespace(ns);
Expand Down Expand Up @@ -1065,22 +1088,22 @@ translation_unit_visitor::create_objc_interface_declaration(
void translation_unit_visitor::process_record_parent(
clang::RecordDecl *cls, class_ &c, const namespace_ &ns)
{
// NOTE: `parent` here means class or structure is nested in the parent,
// not inheritance
const auto *parent = cls->getParent();

std::optional<eid_t> id_opt;

auto parent_ns = ns;
if (parent != nullptr) {
const auto *parent_record_decl =
clang::dyn_cast<clang::RecordDecl>(parent);

if (parent_record_decl != nullptr) {
// NOTE: `parent` here means class or structure is nested in the parent,
// not inheritance
const auto *parent = cls->getParent();

if (parent != nullptr) {
if (const auto *parent_record_decl =
clang::dyn_cast<clang::RecordDecl>(parent);
parent_record_decl != nullptr) {
parent_ns = common::get_tag_namespace(*parent_record_decl);

eid_t ast_id{parent_record_decl->getID()};

// First check if the parent has been added to the diagram as
// regular class
id_opt = id_mapper().get_global_id(ast_id);
Expand All @@ -1097,52 +1120,27 @@ void translation_unit_visitor::process_record_parent(
}
}

if (id_opt && diagram().find<class_>(*id_opt)) {
// Here we have 2 options, either:
// - the parent is a regular C++ class/struct
// - the parent is a class template declaration/specialization
auto parent_class = diagram().find<class_>(*id_opt);

c.set_namespace(parent_ns);
const auto cls_name = cls->getNameAsString();
if (cls_name.empty()) {
// Nested structs can be anonymous
if (anonymous_struct_relationships_.count(cls->getID()) > 0) {
const auto &[label, hint, access, destination_multiplicity] =
anonymous_struct_relationships_[cls->getID()];

c.set_name(parent_class.value().name() + "##" +
fmt::format("({})", label));

std::string destination_multiplicity_str{};
if (destination_multiplicity.has_value()) {
destination_multiplicity_str =
std::to_string(*destination_multiplicity);
}

parent_class.value().add_relationship(
{hint, common::to_id(c.full_name(false)), access, label, "",
destination_multiplicity_str});
}
else
c.set_name(parent_class.value().name() + "##" +
fmt::format(
"(anonymous_{})", std::to_string(cls->getID())));
}
else {
c.set_name(
parent_class.value().name() + "##" + cls->getNameAsString());
}
const auto *lexical_parent = cls->getLexicalParent();
if (lexical_parent != nullptr) {
if (const auto *parent_interface_decl =
clang::dyn_cast<clang::ObjCInterfaceDecl>(lexical_parent);
parent_interface_decl != nullptr) {

c.set_id(common::to_id(c.full_name(false)));
eid_t ast_id{parent_interface_decl->getID()};

if (!cls->getNameAsString().empty()) {
// Don't add anonymous structs as contained in the class
// as they are already added as aggregations
c.add_relationship({relationship_t::kContainment, *id_opt});
// First check if the parent has been added to the diagram as
// regular class
id_opt = id_mapper().get_global_id(ast_id);
}
}

if (id_opt && diagram().find<class_>(*id_opt)) {
process_record_parent_by_type<class_>(*id_opt, c, parent_ns, cls);
}

c.nested(true);
if (id_opt && diagram().find<objc_interface>(*id_opt)) {
process_record_parent_by_type<objc_interface>(
*id_opt, c, parent_ns, cls);
}
}

Expand Down
54 changes: 54 additions & 0 deletions src/class_diagram/visitor/translation_unit_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ class translation_unit_visitor
*/
template_builder_t &tbuilder() { return template_builder_; }

template <typename T>
void process_record_parent_by_type(eid_t parent_id, class_ &c,
namespace_ parent_ns, const clang::RecordDecl *decl);

template_builder_t template_builder_;

std::map<eid_t, std::unique_ptr<clanguml::class_diagram::model::class_>>
Expand All @@ -557,4 +561,54 @@ class translation_unit_visitor
*/
std::set<std::string> processed_template_qualified_names_;
};

template <typename T>
void translation_unit_visitor::process_record_parent_by_type(eid_t parent_id,
class_ &c, namespace_ parent_ns, const clang::RecordDecl *decl)
{
// Here we have 2 options, either:
// - the parent is a regular C++ class/struct
// - the parent is a class template declaration/specialization
auto parent_class = diagram().find<T>(parent_id);

c.set_namespace(parent_ns);
const auto cls_name = decl->getNameAsString();
if (cls_name.empty()) {
// Nested structs can be anonymous
if (anonymous_struct_relationships_.count(decl->getID()) > 0) {
const auto &[label, hint, access, destination_multiplicity] =
anonymous_struct_relationships_[decl->getID()];

c.set_name(parent_class.value().name() + "##" +
fmt::format("({})", label));

std::string destination_multiplicity_str{};
if (destination_multiplicity.has_value()) {
destination_multiplicity_str =
std::to_string(*destination_multiplicity);
}

parent_class.value().add_relationship(
{hint, common::to_id(c.full_name(false)), access, label, "",
destination_multiplicity_str});
}
else
c.set_name(parent_class.value().name() + "##" +
fmt::format("(anonymous_{})", std::to_string(decl->getID())));
}
else {
c.set_name(
parent_class.value().name() + "##" + decl->getNameAsString());
}

c.set_id(common::to_id(c.full_name(false)));

if (!(decl->getNameAsString().empty())) {
// Don't add anonymous structs as contained in the class
// as they are already added as aggregations
c.add_relationship({relationship_t::kContainment, parent_id});
}

c.nested(true);
}
} // namespace clanguml::class_diagram::visitor
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set(TEST_CASES_REQUIRING_CXX20 t00056 t00058 t00059 t00065 t00069 t00074 t00075)
set(TEST_CASES_REQUIRING_CXX20_MODULES t00070 t00071 t00072
t30012 t30013 t30014 t30015)
set(TEST_CASES_REQUIRING_CUDA t20049 t20050 t20051)
set(TEST_CASES_REQUIRING_OBJC t00084 t00085 t20057 t20058 t30016 t40004)
set(TEST_CASES_REQUIRING_OBJC t00084 t00085 t00086 t20057 t20058 t30016 t40004)

if(ENABLE_OBJECTIVE_C_TEST_CASES)
message(STATUS "Enabling Objective-C test cases")
Expand Down
10 changes: 10 additions & 0 deletions tests/t00086/.clang-uml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
diagrams:
t00086_class:
type: class
glob:
- t00086.m
include:
paths:
- .


26 changes: 26 additions & 0 deletions tests/t00086/t00086.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#import <Foundation/NSObject.h>

@interface t00086_a : NSObject <NSCopying, NSMutableCopying> {
@public
enum Color { Red, Green, Blue };
struct Nested {
int _n;
};
struct {
NSUInteger _one : 1;
NSUInteger _two : 1;
NSUInteger _reserved : 30;
} _flagSet;
union {
struct {
char *_foo;
} _foo;
struct {
void *_bar1;
enum Color _bar2;
} _bar;
} _data;

struct Nested *_nested;
}
@end
49 changes: 49 additions & 0 deletions tests/t00086/test_case.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* tests/t00086/test_case.h
*
* Copyright (c) 2021-2024 Bartek Kryza <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

TEST_CASE("t00086")
{
using namespace clanguml::test;
using namespace std::string_literals;

auto [config, db, diagram, model] =
CHECK_CLASS_MODEL("t00086", "t00086_class");

CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) {
REQUIRE(IsObjCInterface(src, "t00086_a"));
REQUIRE(IsInnerClass(src, "t00086_a", "t00086_a::Nested"));
REQUIRE(IsEnum(src, "t00086_a::Color"));
REQUIRE(IsClass(src, "t00086_a::(_flagSet)"));
REQUIRE(IsUnion(src, "t00086_a::(_data)"));
REQUIRE(IsClass(src, "t00086_a::(_data)::(_foo)"));
REQUIRE(IsClass(src, "t00086_a::(_data)::(_bar)"));

REQUIRE(IsAssociation<Public>(
src, "t00086_a", "t00086_a::Nested", "_nested"));

REQUIRE(IsAggregation<Public>(
src, "t00086_a", "t00086_a::(_flagSet)", "_flagSet"));
REQUIRE(IsAggregation<Public>(
src, "t00086_a", "t00086_a::(_data)", "_data"));

REQUIRE(IsAggregation<Public>(
src, "t00086_a::(_data)", "t00086_a::(_data)::(_foo)", "_foo"));
REQUIRE(IsAggregation<Public>(
src, "t00086_a::(_data)", "t00086_a::(_data)::(_bar)", "_bar"));
});
}
5 changes: 3 additions & 2 deletions tests/test_cases.cc
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config,
#if defined(ENABLE_OBJECTIVE_C_TEST_CASES)
#include "t00084/test_case.h"
#include "t00085/test_case.h"
#include "t00086/test_case.h"
#endif

///
Expand Down Expand Up @@ -699,8 +700,8 @@ int main(int argc, char *argv[])

clanguml::cli::cli_handler clih;

std::vector<const char *> argvv = {"clang-uml",
"--config", "./test_config_data/simple.yml"};
std::vector<const char *> argvv = {
"clang-uml", "--config", "./test_config_data/simple.yml"};

argvv.push_back("-q");

Expand Down
3 changes: 3 additions & 0 deletions tests/test_cases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ test_cases:
- name: t00085
title: Objective-C test case for various class members and methods
description:
- name: t00086
title: Objective-C nested structs and enums test case
description:
Sequence diagrams:
- name: t20001
title: Basic sequence diagram test case
Expand Down

0 comments on commit d8df7dc

Please sign in to comment.