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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ private CppLiterals() {}
* as three-digit octal escapes (never ambiguous before a following digit, unlike {@code \x}).
*/
static String stringLiteral(String text) {
StringBuilder out = new StringBuilder("\"");
return "\"" + escapeStringBody(text) + "\"";
}

/**
* Escapes arbitrary text for inclusion <em>inside</em> a double-quoted C++ string literal,
* without the surrounding quotes — for splicing model-controlled text into a larger literal.
* Non-ASCII and control bytes become three-digit octal escapes (never ambiguous before a
* following digit).
*/
static String escapeStringBody(String text) {
StringBuilder out = new StringBuilder();
for (byte raw : text.getBytes(StandardCharsets.UTF_8)) {
int b = raw & 0xFF;
switch (b) {
Expand All @@ -30,7 +40,19 @@ static String stringLiteral(String text) {
}
}
}
return out.append('"').toString();
return out.toString();
}

/**
* A C++ literal for a 64-bit integer. {@code Long.MIN_VALUE} cannot be written as {@code
* -9223372036854775808} — C++ parses that as negation of a value one past {@code int64_t} max,
* which is ill-formed — so it is emitted with the header-free {@code INT64_MIN} idiom.
*/
static String int64Literal(long value) {
// Only the minimum needs special handling; every other int64 magnitude is a
// valid decimal literal that promotes to a wide enough type on assignment or
// comparison, so those are left byte-for-byte unchanged.
return value == Long.MIN_VALUE ? "(-9223372036854775807LL - 1)" : String.valueOf(value);
}

/** Text of a double literal; guarantees a decimal point or exponent so the type stays double. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ static String literal(CppContext context, MemberShape member) {
+ CppLiterals.stringLiteral(value.expectStringNode().getValue())
+ ")";
case BOOLEAN -> value.expectBooleanNode().getValue() ? "true" : "false";
case BYTE, SHORT, INTEGER, LONG ->
String.valueOf(value.expectNumberNode().getValue().longValue());
case BYTE, SHORT, INTEGER -> String.valueOf(value.expectNumberNode().getValue().longValue());
case LONG -> CppLiterals.int64Literal(value.expectNumberNode().getValue().longValue());
case INT_ENUM ->
"static_cast<" + type + ">(" + value.expectNumberNode().getValue().longValue() + ")";
case FLOAT -> "static_cast<float>(" + value.expectNumberNode().getValue().doubleValue() + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,53 @@ static String enumConstant(String memberName) {
return "k" + CaseUtils.toPascalCase(memberName.toLowerCase().replace('-', '_'));
}

/**
* Fails generation when two members of the same shape fold to one C++ name, or a member collides
* with a reserved synthetic name. Enum-constant and union-factory naming lower-case, strip
* separators, or PascalCase the member name, so distinct Smithy members ({@code fooBar} and
* {@code foo_bar}, or a member literally named {@code unknown}) can produce a duplicate
* enumerator/method that no longer compiles. Catching it here names both members and the fix
* instead of surfacing a C++ redefinition error in the generated output.
*/
private static void requireDistinctNames(
String kind,
Object shapeId,
java.util.LinkedHashMap<String, String> foldedByMember,
java.util.Set<String> reserved) {
java.util.Map<String, String> owner = new java.util.HashMap<>();
for (var entry : foldedByMember.entrySet()) {
String member = entry.getKey();
String folded = entry.getValue();
if (reserved.contains(folded)) {
throw new software.amazon.smithy.codegen.core.CodegenException(
"cpp-codegen: "
+ kind
+ " "
+ shapeId
+ " member '"
+ member
+ "' maps to the reserved generated name '"
+ folded
+ "'; rename the member");
}
String prior = owner.putIfAbsent(folded, member);
if (prior != null) {
throw new software.amazon.smithy.codegen.core.CodegenException(
"cpp-codegen: "
+ kind
+ " "
+ shapeId
+ " members '"
+ prior
+ "' and '"
+ member
+ "' both map to the generated name '"
+ folded
+ "'; rename one so their generated names differ");
}
}
}

/**
* Forward declarations for recursive member targets: on a cycle, the target's definition may come
* later in types.h. Boxed members and std::vector elements only need the name declared; duplicate
Expand Down Expand Up @@ -121,6 +168,11 @@ void generateStructure(StructureShape shape) {
void generateEnum(EnumShape shape) {
String name = typeName(shape);
Map<String, String> values = shape.getEnumValues();
java.util.LinkedHashMap<String, String> folded = new java.util.LinkedHashMap<>();
for (String memberName : values.keySet()) {
folded.put(memberName, enumConstant(memberName));
}
requireDistinctNames("enum", shape.getId(), folded, java.util.Set.of("kUnknown"));
writer.addInclude("<string>").addInclude("<string_view>");

writeDocs(shape);
Expand Down Expand Up @@ -179,6 +231,11 @@ void generateEnum(EnumShape shape) {

void generateIntEnum(IntEnumShape shape) {
String name = typeName(shape);
java.util.LinkedHashMap<String, String> folded = new java.util.LinkedHashMap<>();
for (String memberName : shape.getEnumValues().keySet()) {
folded.put(memberName, enumConstant(memberName));
}
requireDistinctNames("intEnum", shape.getId(), folded, java.util.Set.of());
writer.addInclude("<cstdint>");
writeDocs(shape);
writer.openBlock("enum class $L : std::int32_t {", name);
Expand All @@ -192,6 +249,12 @@ void generateIntEnum(IntEnumShape shape) {
void generateUnion(UnionShape shape) {
String name = typeName(shape);
List<MemberShape> members = List.copyOf(shape.members());
java.util.LinkedHashMap<String, String> folded = new java.util.LinkedHashMap<>();
for (MemberShape member : members) {
folded.put(
member.getMemberName(), "From" + CaseUtils.toPascalCase(symbols().toMemberName(member)));
}
requireDistinctNames("union", shape.getId(), folded, java.util.Set.of());
writer.addInclude("<utility>").addInclude("<variant>");

writeDocs(shape);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,18 @@ private static String plainNumber(BigDecimal value) {
return value.stripTrailingZeros().toPlainString();
}

/**
* The C++ literal for a @range bound used in the *comparison* (the failure message keeps the
* plain decimal via {@link #plainNumber}). Identical to plainNumber except int64 min, which is
* not a writable decimal literal in C++.
*/
private static String rangeBoundExpr(BigDecimal value) {
if (value.scale() <= 0 && value.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) == 0) {
return CppLiterals.int64Literal(Long.MIN_VALUE);
}
return plainNumber(value);
}

private void writeLengthCheck(
CppWriter w,
Shape target,
Expand Down Expand Up @@ -429,22 +441,22 @@ private void writeRangeCheck(CppWriter w, RangeTrait range, String valueExpr, St
condition =
valueExpr
+ " < "
+ plainNumber(min.get())
+ rangeBoundExpr(min.get())
+ " || "
+ valueExpr
+ " > "
+ plainNumber(max.get());
+ rangeBoundExpr(max.get());
constraintText =
"Member must be between "
+ plainNumber(min.get())
+ " and "
+ plainNumber(max.get())
+ ", inclusive";
} else if (min.isPresent()) {
condition = valueExpr + " < " + plainNumber(min.get());
condition = valueExpr + " < " + rangeBoundExpr(min.get());
constraintText = "Member must be greater than or equal to " + plainNumber(min.get());
} else {
condition = valueExpr + " > " + plainNumber(max.get());
condition = valueExpr + " > " + rangeBoundExpr(max.get());
constraintText = "Member must be less than or equal to " + plainNumber(max.get());
}
w.openBlock("if ($L) {", condition);
Expand Down Expand Up @@ -493,6 +505,18 @@ private void writePatternCheck(
*/
private static void rejectUnsupportedPattern(PatternTrait pattern) {
String value = pattern.getValue();
// The pattern is emitted verbatim inside R"__smithy(...)__smithy". A value
// containing the closing sequence would terminate the raw literal early and
// produce uncompilable code; reject it rather than emit broken output.
if (value.contains(")__smithy\"")) {
throw new software.amazon.smithy.codegen.core.CodegenException(
"@pattern "
+ value
+ " (at "
+ pattern.getSourceLocation()
+ ") contains the raw-string delimiter sequence )__smithy\", which cannot be emitted"
+ " safely; rewrite the pattern to avoid that literal sequence");
}
boolean inClass = false;
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
Expand Down Expand Up @@ -570,7 +594,13 @@ private void writeEnumCheck(CppWriter w, Shape target, String valueExpr, String
.expectTrait(software.amazon.smithy.model.traits.EnumValueTrait.class)
.expectStringValue())
.toList();
String set = String.join(", ", values);
// The wire values are model-controlled, so escape them for the literal — a
// value containing " or \ would otherwise break the emitted string. Escaping
// in place keeps the message byte-identical for the common (safe) case.
String set =
values.stream()
.map(CppLiterals::escapeStringBody)
.collect(java.util.stream.Collectors.joining(", "));
String type = context.cppSymbols().toSymbol(target).getName();
w.openBlock("if ($L.value() == $L::Value::kUnknown) {", valueExpr, type);
w.write(
Expand Down
Loading
Loading