Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

190 lines
8.2 KiB
C

/*
* repro_issue382.c — Reproduce-first case for OPEN bug #382.
*
* Bug #382: "Java: @Annotation, signatures, and all AST properties missing
* from graph nodes"
*
* Root cause (confirmed by maintainer + reporter re-open):
* extract_decorators() in internal/cbm/extract_defs.c first scans
* ts_node_prev_sibling() looking for nodes of type "annotation" /
* "marker_annotation". In the Java AST emitted by tree-sitter-java, those
* nodes are NOT prev-siblings of either the class_declaration or the
* method_declaration — they live INSIDE the node's own `modifiers` child:
*
* class_declaration
* modifiers
* marker_annotation <- @Entity
* marker_annotation <- @RestController
* type_identifier: "User"
* class_body
* method_declaration
* modifiers
* marker_annotation <- @Override
* annotation <- @GetMapping("/users")
* type_identifier: "String"
* ...
*
* The code does have a fallback that calls find_jvm_modifiers() to search
* the `modifiers` child when prev-sibling count == 0, which covers the
* simple @GetMapping-on-method case already tested in test_extraction.c
* (extract_java_method_annotations_issue382, which passes green on v0.7.0).
*
* What is NOT covered by that existing test:
* a) CLASS-LEVEL annotations (@Entity, @RestController) on the class node
* itself — the existing test only extracts Method nodes; it never
* checks the Class node's .decorators.
* b) marker_annotation (no-arg form, e.g. @Override, @Entity) on methods
* — the existing test uses @GetMapping("/x") which is a full
* `annotation` node with arguments and does a substring match against
* the whole text "@GetMapping(\"/x\")". marker_annotations have a
* different tree-sitter node type and are historically mis-counted.
* c) Multiple stacked annotations on a single method/class.
*
* These cases regress when the fallback path is absent or broken (e.g. the
* fix only wired the method path, not the class path, or it works for
* `annotation` nodes but not `marker_annotation`).
*
* Expected (correct) behaviour:
* - The Class def for "User" carries decorators:
* decorators[0] contains "Entity"
* decorators[1] contains "RestController" (or vice-versa)
* - The Method def for "getUser" carries decorators:
* at least one entry contains "Override"
* at least one entry contains "GetMapping"
* - method "getUser" has a non-empty signature.
*
* Actual (buggy) behaviour:
* - Class def for "User": decorators == NULL (no annotations extracted)
* - Method def for "getUser": marker_annotation @Override is dropped;
* decorators may be NULL or miss @Override.
* → assertions below are RED on current code if either path is broken.
*
* Why this is STRONGER than the existing test_extraction.c #382 reference:
* 1. It asserts decorators on the CLASS node — never checked before.
* 2. It specifically asserts that a marker_annotation (@Override, @Entity)
* is captured, not just a full annotation with arguments.
* 3. It asserts BOTH annotations on a multi-annotated class, exercising the
* count loop that must find > 1 entry.
* 4. It uses ASSERT_NOT_NULL(m->decorators) before touching decorators[i],
* so a NULL decorators field fails loudly rather than crashing/skipping.
*/
#include "test_framework.h"
#include "cbm.h"
/* Convenience: extract one file, return result (caller frees). */
static CBMFileResult *rx(const char *src, CBMLanguage lang,
const char *proj, const char *path) {
return cbm_extract_file(src, (int)strlen(src), lang, proj, path,
0, NULL, NULL);
}
/* Return the first definition whose label AND name both match (either may be
* NULL to wildcard). Mirrors the helper in repro_extraction.c. */
static CBMDefinition *find_def(CBMFileResult *r, const char *label,
const char *name) {
for (int i = 0; i < r->defs.count; i++) {
CBMDefinition *d = &r->defs.items[i];
if (label && (!d->label || strcmp(d->label, label) != 0))
continue;
if (name && (!d->name || strcmp(d->name, name) != 0))
continue;
return d;
}
return NULL;
}
/* Return 1 if any entry in the NULL-terminated decorators array contains
* needle as a substring. */
static int decorators_contain(const CBMDefinition *d, const char *needle) {
if (!d || !d->decorators)
return 0;
for (int i = 0; d->decorators[i]; i++) {
if (strstr(d->decorators[i], needle))
return 1;
}
return 0;
}
/* ───────────────────────────────────────────────────────────────────
* repro_issue382_java_annotations_on_nodes
*
* Asserts that BOTH the Class node AND the Method node produced by
* cbm_extract_file carry their Java annotations in .decorators:
*
* @Entity
* @RestController
* public class User {
* @Override
* @GetMapping("/users")
* public String getUser(String id) { return id; }
* }
*
* RED if:
* • The Class "User" has decorators == NULL (class-level annots dropped)
* • The Class "User" decorators do not contain "Entity"
* • The Class "User" decorators do not contain "RestController"
* • The Method "getUser" has decorators == NULL (method-level annots dropped)
* • The Method "getUser" decorators do not contain "Override" ← marker_annotation
* • The Method "getUser" decorators do not contain "GetMapping" ← annotation
* • The Method "getUser" has NULL or empty signature
* ─────────────────────────────────────────────────────────────────── */
TEST(repro_issue382_java_annotations_on_nodes) {
CBMFileResult *r = rx(
"@Entity\n"
"@RestController\n"
"public class User {\n"
" @Override\n"
" @GetMapping(\"/users\")\n"
" public String getUser(String id) { return id; }\n"
"}\n",
CBM_LANG_JAVA, "t", "User.java");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
/* ── Class node: two class-level marker_annotations ── */
CBMDefinition *cls = find_def(r, "Class", "User");
ASSERT_NOT_NULL(cls);
/* The Class def MUST carry a non-NULL decorators array.
* RED if class-level annotations are silently dropped. */
ASSERT_NOT_NULL(cls->decorators);
/* @Entity (marker_annotation) must be present on the Class. */
ASSERT_TRUE(decorators_contain(cls, "Entity"));
/* @RestController (marker_annotation) must also be present. */
ASSERT_TRUE(decorators_contain(cls, "RestController"));
/* ── Method node: one marker_annotation + one annotation ── */
CBMDefinition *method = find_def(r, "Method", "getUser");
ASSERT_NOT_NULL(method);
/* Method decorators must be non-NULL. */
ASSERT_NOT_NULL(method->decorators);
/* @Override is a marker_annotation (no argument list) — historically
* the most likely to be missed if the extractor only handles the
* `annotation` node type but not `marker_annotation`. */
ASSERT_TRUE(decorators_contain(method, "Override"));
/* @GetMapping("/users") is a full annotation (with argument) — this is
* what the existing test_extraction.c case checks; include it here too
* so we catch any regression. */
ASSERT_TRUE(decorators_contain(method, "GetMapping"));
/* Signature must be extracted: Java method_declaration has a `parameters`
* field that the extractor reads into def.signature. */
ASSERT_NOT_NULL(method->signature);
ASSERT_TRUE(method->signature[0] != '\0');
cbm_free_result(r);
PASS();
}
/* ── Suite ──────────────────────────────────────────────────────── */
SUITE(repro_issue382) {
RUN_TEST(repro_issue382_java_annotations_on_nodes);
}