Skip to content

Commit 458d18e

Browse files
committed
fix(schema): admit L4's compound body-node ids so an -a 4 payload validates
The localId pattern was written before L4 and admitted only a bare line:col or an @-tagged synthetic vertex. SdgVertices keys a call site's actuals off the site's own local id — <line:col>/actual_in:<i> and <line:col>/actual_out — so every one of them was rejected and an -a 4 document failed this repository's own conformance gate: 2936 errors on daytrader8, all of them these two forms. The pattern now enumerates the two compound forms rather than loosening its character class, so ids no builder emits are still rejected: an actual_in missing its ordinal, an actual_out carrying one, an unknown suffix, a base that is not a position, and a bare trailing slash all remain invalid. The emitted ids were correct throughout; only the schema describing them was stale. -a 1, -a 2 and -a 3 documents already validated, which also settles the question the issue raised about whether lower levels shared the gap. L4GateTest now validates the real -a 4 document it already builds, and L4SchemaOracleTest covers the accept and reject cases directly. Closes #207
1 parent b92e3e9 commit 458d18e

3 files changed

Lines changed: 154 additions & 2 deletions

File tree

src/test/java/com/ibm/cldk/schema/L4GateTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,23 @@
55
import static org.junit.jupiter.api.Assertions.assertNotNull;
66
import static org.junit.jupiter.api.Assertions.assertTrue;
77

8+
import com.fasterxml.jackson.databind.ObjectMapper;
89
import com.google.gson.JsonArray;
910
import com.google.gson.JsonElement;
1011
import com.google.gson.JsonObject;
1112
import com.google.gson.JsonParser;
13+
import com.networknt.schema.JsonSchema;
14+
import com.networknt.schema.JsonSchemaFactory;
15+
import com.networknt.schema.SpecVersion;
16+
import com.networknt.schema.ValidationMessage;
17+
import java.io.InputStream;
1218
import java.nio.file.Files;
1319
import java.nio.file.Path;
1420
import java.util.HashSet;
1521
import java.util.LinkedHashMap;
1622
import java.util.Map;
1723
import java.util.Set;
24+
import java.util.stream.Collectors;
1825
import org.junit.jupiter.api.Assumptions;
1926
import org.junit.jupiter.api.BeforeAll;
2027
import org.junit.jupiter.api.Test;
@@ -105,6 +112,25 @@ void overlayCountsAreExactlyWhatTheFixtureImplies() {
105112
assertEquals(6, summaries, "summary: one shortcut per pass-through call site");
106113
}
107114

115+
/**
116+
* The conformance oracle has to cover the level that introduces the compound vertices, not just
117+
* the levels below it: {@code SdgVertices} keys its actuals {@code <call-local>/actual_in:<i>}
118+
* and {@code <call-local>/actual_out}, and a {@code localId} pattern written before L4 rejects
119+
* every one of them (#207). Validating the real {@code -a 4} document is what catches that.
120+
*/
121+
@Test
122+
void theLevel4DocumentValidatesAgainstTheV2Schema() throws Exception {
123+
try (InputStream in = L4GateTest.class.getResourceAsStream("/schema/analysis.v2.schema.json")) {
124+
assertNotNull(in, "the canonical v2 schema must be on the test classpath");
125+
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012).getSchema(in);
126+
Set<ValidationMessage> problems =
127+
schema.validate(new ObjectMapper().readTree(root.toString()));
128+
assertTrue(problems.isEmpty(), "the -a 4 document must validate, but got:\n "
129+
+ problems.stream().map(ValidationMessage::getMessage).limit(10)
130+
.collect(Collectors.joining("\n ")));
131+
}
132+
}
133+
108134
@Test
109135
void semanticDdgAddsToNotReplacesSsa() {
110136
JsonObject heap = callable(root, "Heap", "roundTrip(int)");
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.ibm.cldk.schema;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.networknt.schema.JsonSchema;
9+
import com.networknt.schema.JsonSchemaFactory;
10+
import com.networknt.schema.SpecVersion;
11+
import com.networknt.schema.ValidationMessage;
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
16+
import org.junit.jupiter.api.Test;
17+
18+
/**
19+
* The oracle for the body-node ids L4 introduces, tested against hand-authored payloads rather than
20+
* through the producer — the sibling of {@link L3SchemaOracleTest}, one level up.
21+
*
22+
* <p>{@code SdgVertices} keys a call site's actuals off the site's own local id:
23+
* {@code <line:col>/actual_in:<i>} and {@code <line:col>/actual_out}. The formals it mints are
24+
* {@code @}-tagged and were already admitted; the actuals were not, so an {@code -a 4} payload
25+
* failed this repository's own conformance gate (#207). The pattern enumerates those two forms
26+
* instead of loosening its character class, so the rejections below are the point of the fix: a
27+
* pattern widened to accept anything with a slash in it would gate nothing.
28+
*/
29+
class L4SchemaOracleTest {
30+
31+
private static final ObjectMapper MAPPER = new ObjectMapper();
32+
33+
/** A level-4 payload whose single callable carries one body node under the given local id. */
34+
private static String payload(String localId, String kind) {
35+
String callable = "{"
36+
+ "\"id\":\"can://java/myapp/Foo.java/Foo/m()\","
37+
+ "\"kind\":\"method\","
38+
+ "\"signature\":\"m()\","
39+
+ "\"body\":{\"" + localId + "\":{\"kind\":\"" + kind + "\"}}"
40+
+ "}";
41+
String type = "{"
42+
+ "\"id\":\"can://java/myapp/Foo.java/Foo\","
43+
+ "\"kind\":\"class\","
44+
+ "\"callables\":{\"m()\":" + callable + "}}";
45+
String module = "{"
46+
+ "\"id\":\"can://java/myapp/Foo.java\","
47+
+ "\"kind\":\"module\","
48+
+ "\"source\":\"\","
49+
+ "\"types\":{\"Foo\":" + type + "}}";
50+
return "{"
51+
+ "\"schema_version\":\"2.0.0\","
52+
+ "\"language\":\"java\","
53+
+ "\"max_level\":4,"
54+
+ "\"application\":{"
55+
+ "\"id\":\"can://java/myapp\","
56+
+ "\"kind\":\"application\","
57+
+ "\"symbol_table\":{\"Foo.java\":" + module + "}}}";
58+
}
59+
60+
private static Set<ValidationMessage> validate(String json) throws IOException {
61+
try (InputStream in = L4SchemaOracleTest.class.getResourceAsStream("/schema/analysis.v2.schema.json")) {
62+
assertNotNull(in, "the canonical v2 schema must be on the test classpath");
63+
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012).getSchema(in);
64+
return schema.validate(MAPPER.readTree(json));
65+
}
66+
}
67+
68+
private static void assertAccepted(String localId, String kind) throws IOException {
69+
Set<ValidationMessage> problems = validate(payload(localId, kind));
70+
assertTrue(problems.isEmpty(), "expected the oracle to accept the local id " + localId + ", but got:\n "
71+
+ problems.stream().map(ValidationMessage::getMessage).collect(Collectors.joining("\n ")));
72+
}
73+
74+
private static void assertRejected(String localId, String because) throws IOException {
75+
assertFalse(validate(payload(localId, "statement")).isEmpty(),
76+
"the oracle must reject the local id " + localId + ": " + because);
77+
}
78+
79+
@Test
80+
void anActualInVertexKeyedOffItsCallSiteValidates() throws IOException {
81+
assertAccepted("3:39/actual_in:0", "actual_in");
82+
}
83+
84+
@Test
85+
void anActualOutVertexKeyedOffItsCallSiteValidates() throws IOException {
86+
assertAccepted("3:39/actual_out", "actual_out");
87+
}
88+
89+
@Test
90+
void theFormalVerticesStillValidate() throws IOException {
91+
assertAccepted("@formal_in:0", "formal_in");
92+
assertAccepted("@formal_out", "formal_out");
93+
}
94+
95+
@Test
96+
void aPlainPositionStillValidates() throws IOException {
97+
assertAccepted("3:39", "call");
98+
}
99+
100+
@Test
101+
void anActualInWithoutItsIndexIsRejected() throws IOException {
102+
// SdgVertices always numbers an actual_in by the parameter it feeds.
103+
assertRejected("3:39/actual_in", "actual_in carries the argument's ordinal");
104+
}
105+
106+
@Test
107+
void anActualOutCarryingAnIndexIsRejected() throws IOException {
108+
// A call site returns at most one value, so there is no ordinal to carry.
109+
assertRejected("3:39/actual_out:0", "actual_out is unindexed");
110+
}
111+
112+
@Test
113+
void anUnknownSuffixIsRejected() throws IOException {
114+
assertRejected("3:39/actual_side", "only the two forms SdgVertices mints are admitted");
115+
}
116+
117+
@Test
118+
void aCompoundIdWhoseBaseIsNotAPositionIsRejected() throws IOException {
119+
assertRejected("entry/actual_out", "the base of a compound id is the call site's line:col");
120+
}
121+
122+
@Test
123+
void aBareSlashSuffixIsRejected() throws IOException {
124+
assertRejected("3:39/", "a trailing slash names no vertex");
125+
}
126+
}

src/test/resources/schema/analysis.v2.schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333

3434
"localId": {
3535
"type": "string",
36-
"pattern": "^(\\d+:\\d+|@[A-Za-z0-9_:.$/-]+)$",
37-
"description": "Body-node id within a callable: a line:col position, or an @tag for a synthetic vertex."
36+
"pattern": "^(\\d+:\\d+(/actual_in:\\d+|/actual_out)?|@[A-Za-z0-9_:.$/-]+)$",
37+
"description": "Body-node id within a callable: a line:col position, an @tag for a synthetic vertex (@entry, @exit, @formal_in:<i>, @formal_out), or — at level 4 — a call site's own line:col suffixed with the actual it carries (<line:col>/actual_in:<i>, <line:col>/actual_out). The two compound forms are enumerated rather than admitted by a looser character class, so an id no builder emits is still rejected."
3838
},
3939

4040
"span": {

0 commit comments

Comments
 (0)