Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In C++, an alias that names a class through a class derived from the one that declares it, like protocolbuffers/protobuf's `using ConformanceRequestSetting = BinaryAndJsonConformanceSuite::ConformanceRequestSetting;`, now leads to that class, `ConformanceTestSuite::ConformanceRequestSetting`. Before, an alias was followed only to a class declared under exactly the name it spells, so objects created through this one linked to the alias rather than the class. Re-index C++ projects after upgrading.
- In C++, a name written in a googletest `TEST_F` or `TEST_P` body now finds the test fixture's nested types, those of its base classes included, before anything in the surrounding namespace, as C++ does for the member function such a body is: facebook/rocksdb's `new TestUserDefinedIndexFactory()` links to the class nested in the fixture's base. A class declared inside a test body, or inside any function, is now only found from that function, so the local classes many RocksDB tests each declare under one name, like `FakeWF` or `MyManager`, no longer stand in for one another, and a call like `fwf->SetIOError(true)` reaches the `FakeWF` its own test declares. Re-index C++ projects after upgrading.
- In C and C++ tests, a name that several files declare now links to the one the test's own includes declare, when there is one: protocolbuffers/protobuf's C# generator tests call the `TestSourceDir()` that `googletest.h` declares, not another test helper's they never include; its upb tests call `upb_Arena_New()` and the rest of upb's C API in upb's own files, not the copies bundled for PHP and Ruby, which had left those calls unlinked; and facebook/rocksdb's C API test reaches `rocksdb_open` and its siblings in `db/c.cc`. Re-index C and C++ projects after upgrading.
- In C and C++ code outside tests too, a name that several files declare now links to the one the file's own includes declare, when there is one: protocolbuffers/protobuf's Python extension calls functions like `upb_Encode()` in upb's own files rather than the copies bundled for PHP, and facebook/rocksdb's stress tool calls the `StringToHex()` its own header declares rather than `LDBCommand::StringToHex`. Re-index C and C++ projects after upgrading.
- In C++, a declaration continued on a line that starts with `*`, like facebook/rocksdb's `const auto& el =` over `*reinterpret_cast<std::shared_ptr<EventListener>*>(jhandle);`, is now read, so calls such as `el->OnFlushCompleted(…)` link to `EventListener`'s methods; such a line used to be skipped as part of a comment. Code commented out in a `/* … */` block, and the text of a string literal, like protocolbuffers/protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]`, are no longer read as declarations, which had sent a call to the wrong method or to none. Re-index C++ projects after upgrading.
- In C++, a call with a comparison or a shift among its arguments, like protocolbuffers/protobuf's `p->Emit({{"declare", has_bit_word_index < 0 ? … : ""}, …}, "…")` or `WriteVarint(num << 3, s)`, is now read with the right number of arguments, so it links to the overload that fits rather than one taking fewer. A parameter or variable whose type has template arguments is now read even after another such parameter on the same line, like facebook/rocksdb's `const autovector<uint32_t>& cf_ids`, or with empty ones, like fmt's `parse_context<>& ctx`: calls on it link to that class's methods, and calls on a `std::vector` declared that way no longer link to a project class's method of the same name. Re-index C++ projects after upgrading.
- In C++, a call passing `nullptr` now links to the overload that takes a `std::nullptr_t`, as C++ picks it, rather than one taking a pointer, and a call no longer links to an overload declared `= delete`, which no call that compiles can reach: protocolbuffers/protobuf's `ptr.reset(nullptr)` on an `Arena::UniquePtr` reaches `reset(std::nullptr_t)`, and `field.Add("foo")` on a `RepeatedPtrField` reaches `Add(Element&&)` instead of the deleted `Add(const Element&)`. Re-index C++ projects after upgrading.
Expand Down
368 changes: 368 additions & 0 deletions __tests__/cpp-translation-unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
/**
* A C or C++ name is one its translation unit declares, in production code
* as in tests (cpp-test-translation-unit.test.ts). Of several same-named
* candidates, what the file's includes declare goes first: protobuf's Python
* extension calls `upb_Encode()` from upb/wire/encode.h, not the copy the PHP
* extension bundles in php-upb.c; its Java generator calls the `HasHasbit()`
* helpers.h declares, not `ImmutableFieldGenerator::HasHasbit`, a method
* beside the caller. A source implements the header beside it (the
* Python extension's pyext/message.cc is no implementation of
* google/protobuf/message.h), a header's inline overload reaches the
* definition its own source holds, and a call keeps a function outside the
* unit that takes its arguments over one inside that can't (glog's
* three-argument `GetStackTrace(…)` is not utilities.h's `GetStackTrace()`).
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';

let root = '';
let cg: CodeGraph;

beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cpp-tu-'));
const files: Record<string, string> = {
// upb's encoder, and the copy the PHP extension's amalgamation carries.
'upb/wire/encode.h': `#ifndef UPB_WIRE_ENCODE_H_
#define UPB_WIRE_ENCODE_H_

#include <stddef.h>

int upb_Encode(const void* msg, int options, char** buf, size_t* size);

#endif
`,
'upb/wire/encode.c': `#include "upb/wire/encode.h"

int upb_Encode(const void* msg, int options, char** buf, size_t* size) {
*size = 0;
return 0;
}
`,
'php/ext/google/protobuf/php-upb.h': `#include <stddef.h>

int upb_Encode(const void* msg, int options, char** buf, size_t* size);
`,
'php/ext/google/protobuf/php-upb.c': `#include "php-upb.h"

int upb_Encode(const void* msg, int options, char** buf, size_t* size) {
*size = 0;
return 0;
}
`,
'python/message.c': `#include "upb/wire/encode.h"

int PyUpb_Message_Serialize(const void* msg) {
char* buf;
size_t size;
return upb_Encode(msg, 0, &buf, &size);
}
`,
// A free function the Java generator's helpers.h declares, and a field
// generator's method of the same name beside the caller.
'src/google/protobuf/compiler/java/helpers.h': `namespace google {
namespace protobuf {
class FieldDescriptor;
namespace compiler {
namespace java {

inline bool HasHasbit(const FieldDescriptor* descriptor) {
return descriptor != nullptr;
}

} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/compiler/java/full/field_generator.h': `namespace google {
namespace protobuf {
class FieldDescriptor;
namespace compiler {
namespace java {

class ImmutableFieldGenerator {
public:
bool HasHasbit() const;

private:
const FieldDescriptor* descriptor_;
};

} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/compiler/java/full/field_generator.cc': `#include "google/protobuf/compiler/java/full/field_generator.h"

#include "google/protobuf/compiler/java/helpers.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace java {

bool ImmutableFieldGenerator::HasHasbit() const {
return ::google::protobuf::compiler::java::HasHasbit(descriptor_);
}

} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/compiler/java/full/enum_field.cc': `#include "google/protobuf/compiler/java/helpers.h"
#include "google/protobuf/compiler/java/full/field_generator.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace java {

namespace {

int SetEnumVariables(const FieldDescriptor* descriptor) {
if (HasHasbit(descriptor)) {
return 1;
}
return 0;
}

} // namespace

} // namespace java
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
// A header's template overload calls the one its own source defines.
'conformance/binary_wireformat.h': `#include <cstdint>
#include <string>

namespace google {
namespace protobuf {
namespace conformance {

using Wire = std::string;

Wire Varint(uint64_t x);

// Overloads to avoid forcing sign/size conversion warnings on call-sites.
template <typename T>
Wire Varint(T x) {
return Varint(static_cast<uint64_t>(x));
}

} // namespace conformance
} // namespace protobuf
} // namespace google
`,
'conformance/binary_wireformat.cc': `#include "conformance/binary_wireformat.h"

namespace google {
namespace protobuf {
namespace conformance {

Wire Varint(uint64_t x) {
return std::string(1, static_cast<char>(x));
}

} // namespace conformance
} // namespace protobuf
} // namespace google
`,
// glog's three-parameter GetStackTrace is declared in stacktrace.h and
// defined in a header stacktrace.cc includes; utilities.h declares a
// GetStackTrace() of no parameters.
'src/stacktrace.h': `namespace google {
namespace glog_internal_namespace_ {

int GetStackTrace(void** result, int max_depth, int skip_count);

} // namespace glog_internal_namespace_
} // namespace google
`,
'src/stacktrace_generic-inl.h': `#include "stacktrace.h"

namespace google {
namespace glog_internal_namespace_ {

int GetStackTrace(void** result, int max_depth, int skip_count) {
return 0;
}

} // namespace glog_internal_namespace_
} // namespace google
`,
'src/stacktrace.cc': `#include "stacktrace.h"

#include "stacktrace_generic-inl.h"
`,
'src/utilities.h': `#include <string>

namespace google {

std::string GetStackTrace();

} // namespace google
`,
'src/utilities.cc': `#include "utilities.h"

namespace google {

std::string GetStackTrace() {
return "";
}

} // namespace google
`,
'src/logging.cc': `#include <string>

#include "stacktrace.h"
#include "utilities.h"

namespace google {

using namespace glog_internal_namespace_;

int RecordCrashReason(void** stack) {
std::string trace = GetStackTrace();
int depth = GetStackTrace(stack, 32, 4);
return depth + static_cast<int>(trace.size());
}

} // namespace google
`,
// The Python extension's message.cc implements pyext/message.h beside
// it, though it includes google/protobuf/message.h too.
'src/google/protobuf/message.h': `namespace google {
namespace protobuf {

class Message {
public:
virtual ~Message() {}
};

} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/any.pb.h': `#include "google/protobuf/message.h"

namespace google {
namespace protobuf {

class Any : public Message {
public:
void MergeFrom(const Any& from) {}
};

} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/descriptor.pb.h': `#include "google/protobuf/message.h"

namespace google {
namespace protobuf {

class FileDescriptorSet : public Message {
public:
void CopyFrom(const FileDescriptorSet& from);
};

} // namespace protobuf
} // namespace google
`,
'src/google/protobuf/descriptor.pb.cc': `#include "google/protobuf/descriptor.pb.h"

namespace google {
namespace protobuf {

void FileDescriptorSet::CopyFrom(const FileDescriptorSet& from) {
if (&from == this) return;
MergeFrom(from);
}

} // namespace protobuf
} // namespace google
`,
'python/google/protobuf/pyext/message.h': `namespace google {
namespace protobuf {
namespace python {
namespace cmessage {

int MergeFrom(void* self, void* arg);

} // namespace cmessage
} // namespace python
} // namespace protobuf
} // namespace google
`,
'python/google/protobuf/pyext/message.cc': `#include "google/protobuf/message.h"
#include "google/protobuf/pyext/message.h"

namespace google {
namespace protobuf {
namespace python {
namespace cmessage {

int MergeFrom(void* self, void* arg) { return 0; }

} // namespace cmessage
} // namespace python
} // namespace protobuf
} // namespace google
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});

afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});

/** `qualifiedName (file)` of every call the file's symbols make. */
const callsFrom = (file: string): string[] => {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => {
const target = cg.getNode(e.target)!;
return `${target.qualifiedName} (${target.filePath})`;
});
};

describe('a C/C++ reference links what its translation unit declares', () => {
it('calls the C function its includes declare, not a bundled copy', () => {
const calls = callsFrom('python/message.c');
expect(calls).toContain('upb_Encode (upb/wire/encode.c)');
expect(calls.some((c) => c.includes('php-upb'))).toBe(false);
});

it('calls the free function its includes declare, not a class\'s method beside it', () => {
const calls = callsFrom('src/google/protobuf/compiler/java/full/enum_field.cc');
expect(calls).toContain('google::protobuf::compiler::java::HasHasbit (src/google/protobuf/compiler/java/helpers.h)');
expect(calls.some((c) => c.includes('ImmutableFieldGenerator'))).toBe(false);
});

it('lets a header\'s overload call the definition its own source holds', () => {
const calls = callsFrom('conformance/binary_wireformat.h');
expect(calls).toContain('google::protobuf::conformance::Varint (conformance/binary_wireformat.cc)');
});

it('keeps a call off a function its arguments cannot fill', () => {
const calls = callsFrom('src/logging.cc');
expect(calls).toContain('google::GetStackTrace (src/utilities.cc)');
expect(calls).toContain('google::glog_internal_namespace_::GetStackTrace (src/stacktrace_generic-inl.h)');
});

it('reads a source as implementing the header beside it, not a namesake elsewhere', () => {
const calls = callsFrom('src/google/protobuf/descriptor.pb.cc');
expect(calls.some((c) => c.includes('pyext/message.cc'))).toBe(false);
});
});
Loading