Coverage Report

Created: 2026-08-14 10:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/unoidl/source/sourcetreeprovider.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <sal/config.h>
11
#include <sal/log.hxx>
12
13
#include <map>
14
#include <utility>
15
#include <vector>
16
17
#include <osl/file.h>
18
#include <osl/file.hxx>
19
#include <rtl/character.hxx>
20
#include <rtl/ref.hxx>
21
#include <rtl/ustrbuf.hxx>
22
#include <rtl/ustring.hxx>
23
#include <unoidl/unoidl.hxx>
24
25
#include "sourceprovider-scanner.hxx"
26
#include "sourcetreeprovider.hxx"
27
28
#if defined MACOSX || defined LINUX
29
#include <dirent.h>
30
#include <osl/thread.h>
31
#endif
32
33
namespace unoidl::detail {
34
35
namespace {
36
37
//TODO: Bad hack to work around osl::FileStatus::getFileName not determining the
38
// original spelling of a file name (not even with
39
// osl_FileStatus_Mask_Validate):
40
0
OUString getFileName(OUString const & uri, osl::FileStatus const & status) {
41
0
#if defined MACOSX || defined LINUX
42
0
    sal_Int32 i = uri.lastIndexOf('/') + 1;
43
0
    OUString path;
44
0
    if (osl::FileBase::getSystemPathFromFileURL(uri.copy(0, i), path)
45
0
        != osl::FileBase::E_None)
46
0
    {
47
0
        SAL_WARN(
48
0
            "unoidl",
49
0
            "cannot getSystemPathFromFileURL(" << uri.copy(0, i) << ")");
50
0
        return status.getFileName();
51
0
    }
52
0
    OString dir(OUStringToOString(path, osl_getThreadTextEncoding()));
53
0
    OString name(OUStringToOString(uri.subView(i), osl_getThreadTextEncoding()));
54
0
    DIR * d = opendir(dir.getStr());
55
0
    if (d == nullptr) {
56
0
        SAL_WARN("unoidl", "cannot opendir(" << dir << ")");
57
0
        return status.getFileName();
58
0
    }
59
0
    for (;;) {
60
0
        dirent ent;
61
0
        dirent * p;
62
63
        // readdir_r() is deprecated in current POSIX
64
0
        SAL_WNODEPRECATED_DECLARATIONS_PUSH
65
0
        int e = readdir_r(d, &ent, &p);
66
0
        SAL_WNODEPRECATED_DECLARATIONS_POP
67
68
0
        if (e != 0) {
69
0
            SAL_WARN("unoidl", "cannot readdir_r");
70
0
            closedir(d);
71
0
            return status.getFileName();
72
0
        }
73
0
        if (p == nullptr) {
74
0
            SAL_WARN(
75
0
                "unoidl", "cannot find " << name << " via readdir of " << dir);
76
0
            closedir(d);
77
0
            return status.getFileName();
78
0
        }
79
0
        if (name.equalsIgnoreAsciiCase(p->d_name)) {
80
0
            closedir(d);
81
0
            return OUString(
82
0
                p->d_name, std::strlen(p->d_name), osl_getThreadTextEncoding());
83
0
        }
84
0
    }
85
#else
86
    (void) uri;
87
    return status.getFileName();
88
#endif
89
0
}
90
91
0
bool exists(OUString const & uri, bool directory) {
92
0
    osl::DirectoryItem item;
93
0
    osl::FileStatus status(
94
0
        osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName);
95
0
    return osl::DirectoryItem::get(uri, item) == osl::FileBase::E_None
96
0
        && item.getFileStatus(status) == osl::FileBase::E_None
97
0
        && (status.getFileType() == osl::FileStatus::Directory) == directory
98
0
        && getFileName(uri, status) == uri.subView(uri.lastIndexOf('/') + 1);
99
0
}
100
101
class Cursor: public MapCursor {
102
public:
103
0
    Cursor(Manager& manager, OUString const & uri): manager_(manager), directory_(uri) {
104
0
        auto const rc = directory_.open();
105
0
        SAL_WARN_IF(
106
0
            rc != osl::FileBase::E_None, "unoidl", "open(" << uri << ") failed with " << +rc);
107
0
    }
108
109
private:
110
0
    virtual ~Cursor() noexcept override {}
111
112
    virtual rtl::Reference<Entity> getNext(OUString *) override;
113
114
    Manager& manager_;
115
    osl::Directory directory_;
116
};
117
118
class SourceModuleEntity: public ModuleEntity {
119
public:
120
0
    SourceModuleEntity(Manager& manager, OUString uri): manager_(manager), uri_(std::move(uri)) {}
121
122
private:
123
0
    virtual ~SourceModuleEntity() noexcept override {}
124
125
    virtual std::vector<OUString> getMemberNames() const override
126
0
    { return std::vector<OUString>(); } //TODO
127
128
    virtual rtl::Reference< MapCursor > createCursor() const override
129
0
    { return new Cursor(manager_, uri_); }
130
131
    Manager& manager_;
132
    OUString uri_;
133
};
134
135
0
bool isValidFileName(std::u16string_view name, bool directory) {
136
0
    for (size_t i = 0;; ++i) {
137
0
        if (i == name.size()) {
138
0
            if (i == 0) {
139
0
                return false;
140
0
            }
141
0
            return directory;
142
0
        }
143
0
        auto const c = name[i];
144
0
        if (c == '.') {
145
0
            if (i == 0 || name[i - 1] == '_') {
146
0
                return false;
147
0
            }
148
0
            return !directory && name.substr(i + 1) == u"idl";
149
0
        } else if (c == '_') {
150
            //TODO: Ignore case of name[0] only for case-insensitive file systems:
151
0
            if (i == 0 || name[i - 1] == '_') {
152
0
                return false;
153
0
            }
154
0
        } else if (rtl::isAsciiDigit(c)) {
155
0
            if (i == 0) {
156
0
                return false;
157
0
            }
158
0
        } else if (!rtl::isAsciiAlpha(c)) {
159
0
            return false;
160
0
        }
161
0
    }
162
0
}
163
164
}
165
166
0
rtl::Reference<Entity> Cursor::getNext(OUString * name) {
167
0
    assert(name != nullptr);
168
0
    for (;;) {
169
0
        osl::DirectoryItem i;
170
0
        auto rc = directory_.getNextItem(i);
171
0
        switch (rc) {
172
0
        case osl::FileBase::E_None:
173
0
            {
174
0
                osl::FileStatus stat(
175
0
                    osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
176
0
                    osl_FileStatus_Mask_FileURL);
177
0
                rc = i.getFileStatus(stat);
178
0
                if (rc != osl::FileBase::E_None) {
179
0
                    SAL_WARN(
180
0
                        "unoidl",
181
0
                        "getFileStatus in <" << directory_.getURL() << "> failed with " << +rc);
182
0
                    continue;
183
0
                }
184
0
                auto const dir = stat.getFileType() == osl::FileStatus::Directory;
185
0
                if (!isValidFileName(stat.getFileName(), dir)) {
186
0
                    continue;
187
0
                }
188
0
                if (dir) {
189
                    //TODO: Using osl::FileStatus::getFileName can likely cause issues on case-
190
                    // insensitive/preserving file systems, see the free getFileName function above
191
                    // (which likely goes unnoticed if module identifiers follow the convention of
192
                    // being all-lowercase):
193
0
                    *name = stat.getFileName();
194
0
                    return new SourceModuleEntity(manager_, stat.getFileURL());
195
0
                } else {
196
0
                    SourceProviderScannerData data(&manager_);
197
0
                    if (!parse(stat.getFileURL(), &data)) {
198
0
                        SAL_WARN("unoidl", "cannot parse <" << stat.getFileURL() << ">");
199
0
                        continue;
200
0
                    }
201
0
                    auto ent = data.entities.end();
202
0
                    for (auto j = data.entities.begin(); j != data.entities.end(); ++j) {
203
0
                        if (j->second.kind != SourceProviderEntity::KIND_LOCAL)
204
0
                        {
205
0
                            continue;
206
0
                        }
207
0
                        if (ent != data.entities.end()) {
208
0
                            throw FileFormatException(
209
0
                                stat.getFileURL(), u"source file defines more than one entity"_ustr);
210
0
                        }
211
0
                        ent = j;
212
0
                    }
213
0
                    if (ent == data.entities.end()) {
214
0
                        SAL_INFO(
215
0
                            "unoidl",
216
0
                            "source file <" << stat.getFileURL() << "> defines no entity");
217
0
                        continue;
218
0
                    }
219
                    //TODO: Check that the entity's name matches the suffix of stat.getFileURL():
220
0
                    *name = ent->first.copy(ent->first.lastIndexOf('.') + 1);
221
0
                    return ent->second.entity;
222
0
                }
223
0
            }
224
0
        default:
225
0
            SAL_WARN( "unoidl", "getNext from <" << directory_.getURL() << "> failed with " << +rc);
226
0
            [[fallthrough]];
227
0
        case osl::FileBase::E_NOENT:
228
0
            return {};
229
0
        }
230
0
    }
231
0
}
232
233
SourceTreeProvider::SourceTreeProvider(Manager & manager, OUString const & uri):
234
0
    manager_(manager), uri_(uri.endsWith("/") ? uri : uri + "/")
235
0
{}
236
237
0
rtl::Reference<MapCursor> SourceTreeProvider::createRootCursor() const {
238
0
    return new Cursor(manager_, uri_);
239
0
}
240
241
rtl::Reference<Entity> SourceTreeProvider::findEntity(OUString const & name)
242
    const
243
0
{
244
0
    std::map< OUString, rtl::Reference<Entity> >::iterator ci(
245
0
        cache_.find(name));
246
0
    if (ci != cache_.end()) {
247
0
        return ci->second;
248
0
    }
249
    // Match name against
250
    //   name ::= identifier ("." identifier)*
251
    //   identifier ::= upper-blocks | lower-block
252
    //   upper-blocks ::= upper ("_"? alnum)*
253
    //   lower-block :== lower alnum*
254
    //   alnum ::= digit | upper | lower
255
    //   digit ::= "0"--"9"
256
    //   upper ::= "A"--"Z"
257
    //   lower ::= "a"--"z"
258
0
    OUStringBuffer buf(name);
259
0
    sal_Int32 start = 0;
260
0
    sal_Int32 i = 0;
261
0
    for (; i != name.getLength(); ++i) {
262
0
        sal_Unicode c = name[i];
263
0
        if (c == '.') {
264
0
            assert(i == start || i != 0);
265
0
            if (i == start || name[i - 1] == '_') {
266
0
                throw FileFormatException( //TODO
267
0
                    u""_ustr, "Illegal UNOIDL identifier \"" + name + "\"");
268
0
            }
269
0
            buf[i] = '/';
270
0
            start = i + 1;
271
0
        } else if (c == '_') {
272
0
            assert(i == start || i != 0);
273
0
            if (i == start || name[i - 1] == '_'
274
0
                || !rtl::isAsciiUpperCase(name[start]))
275
0
            {
276
0
                throw FileFormatException( //TODO
277
0
                    u""_ustr, "Illegal UNOIDL identifier \"" + name + "\"");
278
0
            }
279
0
        } else if (rtl::isAsciiDigit(c)) {
280
0
            if (i == start) {
281
0
                throw FileFormatException( //TODO
282
0
                    u""_ustr, "Illegal UNOIDL identifier \"" + name + "\"");
283
0
            }
284
0
        } else if (!rtl::isAsciiAlpha(c)) {
285
0
            throw FileFormatException( //TODO
286
0
                u""_ustr, "Illegal UNOIDL identifier \"" + name + "\"");
287
0
        }
288
0
    }
289
0
    if (i == start) {
290
0
        throw FileFormatException( //TODO
291
0
            u""_ustr, "Illegal UNOIDL identifier \"" + name + "\"");
292
0
    }
293
0
    OUString uri(uri_ + buf);
294
0
    rtl::Reference<Entity> ent;
295
    // Prevent conflicts between foo/ and Foo.idl on case-preserving file
296
    // systems:
297
0
    if (exists(uri, true) && !exists(uri + ".idl", false)) {
298
0
        ent = new SourceModuleEntity(manager_, uri);
299
0
    } else {
300
0
        uri += ".idl";
301
0
        SourceProviderScannerData data(&manager_);
302
0
        if (parse(uri, &data)) {
303
0
            std::map<OUString, SourceProviderEntity>::const_iterator j(
304
0
                data.entities.find(name));
305
0
            if (j != data.entities.end()) {
306
0
                ent = j->second.entity;
307
0
            }
308
0
            SAL_WARN_IF(
309
0
                !ent.is(), "unoidl",
310
0
                "<" << uri << "> does not define entity " << name);
311
0
        }
312
0
    }
313
0
    cache_.emplace(name, ent);
314
0
    return ent;
315
0
}
316
317
0
SourceTreeProvider::~SourceTreeProvider() noexcept {}
318
319
}
320
321
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */