Skip to content

Commit

Permalink
Make string.format checks a little stricter
Browse files Browse the repository at this point in the history
Technically a breaking change, but hopefully not in practice.
  • Loading branch information
SquidDev committed Jul 27, 2023
1 parent 9a6c2a7 commit c2c0275
Show file tree
Hide file tree
Showing 14 changed files with 10,309 additions and 18,891 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -395,12 +395,7 @@ private static int calculateExpWidth(
* MAX_FIXED_DIGITS_AFTER_POINT</code><br/>
* characters (one additional character for the sign, and one for the decimal point).
*/
public static void toFixed(
double value,
int requestedDigits,
FormatOptions formatOptions,
CharBuffer resultBuilder
) {
public static void toFixed(double value, int requestedDigits, FormatOptions formatOptions, CharBuffer resultBuilder) {
// DOUBLE_CONVERSION_ASSERT(MAX_FIXED_DIGITS_BEFORE_POINT == 60);

if (Doubles.isSpecial(value)) {
Expand Down Expand Up @@ -454,15 +449,11 @@ public static void toExponential(double value, int requestedDigits, FormatOption
}

if (requestedDigits < 0) {
throw new IllegalArgumentException(
String.format("requestedDigits must be >= 0. got: %d",
requestedDigits));
throw new IllegalArgumentException(String.format("requestedDigits must be >= 0. got: %d", requestedDigits));
}

if (requestedDigits > MAX_EXPONENTIAL_DIGITS) {
throw new IllegalArgumentException(
String.format("requestedDigits must be less than %d. got: %d",
MAX_EXPONENTIAL_DIGITS, requestedDigits));
throw new IllegalArgumentException(String.format("requestedDigits must be less than %d. got: %d", MAX_EXPONENTIAL_DIGITS, requestedDigits));
}


Expand All @@ -475,12 +466,7 @@ public static void toExponential(double value, int requestedDigits, FormatOption
decimalRep.zeroExtend(requestedDigits + 1);

int exponent = decimalRep.getPointPosition() - 1;
createExponentialRepresentation(decimalRep,
value,
decimalRep.length(),
exponent,
formatOptions,
resultBuilder);
createExponentialRepresentation(decimalRep, value, decimalRep.length(), exponent, formatOptions, resultBuilder);
}

/**
Expand Down Expand Up @@ -533,8 +519,7 @@ public static void toPrecision(double value, int precision, FormatOptions format
}

if (precision < MIN_PRECISION_DIGITS || precision > MAX_PRECISION_DIGITS) {
throw new IllegalArgumentException(String.format(
"argument precision must be in range (%d,%d)", MIN_PRECISION_DIGITS, MAX_PRECISION_DIGITS));
throw new IllegalArgumentException(String.format("argument precision must be in range (%d,%d)", MIN_PRECISION_DIGITS, MAX_PRECISION_DIGITS));
}

// Find a sufficiently precise decimal representation of n.
Expand Down Expand Up @@ -562,18 +547,9 @@ public static void toPrecision(double value, int precision, FormatOptions format
// is allowed to return less characters.
decimalRep.zeroExtend(precision);

createExponentialRepresentation(decimalRep,
value,
precision,
exponent,
formatOptions,
resultBuilder);
createExponentialRepresentation(decimalRep, value, precision, exponent, formatOptions, resultBuilder);
} else {
createDecimalRepresentation(decimalRep,
value,
Math.max(0, precision - decimalRep.getPointPosition()),
formatOptions,
resultBuilder);
createDecimalRepresentation(decimalRep, value, Math.max(0, precision - decimalRep.getPointPosition()), formatOptions, resultBuilder);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cc/tweaked/internal/string/NumberParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,6 @@ private static double scanHexDouble(byte[] bytes, int index, int end) {
exponent += givenExponent;
}

return Math.scalb(result, exponent);
return Math.scalb((double) result, exponent);
}
}
12 changes: 12 additions & 0 deletions src/main/java/org/squiddev/cobalt/Buffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,18 @@ public Buffer append(LuaString str) {
return this;
}

/**
* Append a {@link LuaString} to the buffer.
*
* @param str The string to append
* @return {@code this}, for chaining.
*/
public Buffer append(LuaString str, int start, int srcLength) {
ensure(length);
length = str.copyTo(start, bytes, length, srcLength);
return this;
}

/**
* Append a Java String to the buffer.
* The Java string will be converted to bytes by limiting between 0 and 255
Expand Down
157 changes: 96 additions & 61 deletions src/main/java/org/squiddev/cobalt/lib/FormatDesc.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,45 @@
import org.squiddev.cobalt.LuaString;

public class FormatDesc {
private boolean leftAdjust;
private boolean zeroPad;
private boolean explicitPlus;
private boolean space;
private boolean alternateForm;
private static final int MAX_FLAGS = 5;

private static final DoubleToStringConverter.Symbols LOWER_SYMBOLS =
new DoubleToStringConverter.Symbols("inf", "nan", 'e');
private static final DoubleToStringConverter.Symbols UPPER_SYMBOLS =
new DoubleToStringConverter.Symbols("INF", "NAN", 'E');

private int width;
int precision;
private static final int MAX_FLAGS = 5;
static final int LEFT_ADJUST = 1 << 0;
static final int EXPLICIT_PLUS = 1 << 1;
static final int SPACE = 1 << 2;
static final int ALTERNATE_FORM = 1 << 3;
static final int ZERO_PAD = 1 << 4;
static final int PRECISION = 1 << 5;

private final int flags;
private final int width;
final int precision;

final int conversion;

final LuaString format;
final int start;
final int length;

FormatDesc(LuaString strfrmt, final int start) throws LuaError {
int p = start, n = strfrmt.length();
FormatDesc(LuaString format, final int start) throws LuaError {
this.format = format;
this.start = start;

int p = start, n = format.length();
int c = 0;

int flags = 0;
boolean moreFlags = true;
while (moreFlags) {
switch (c = ((p < n) ? strfrmt.charAt(p++) : 0)) {
case '-' -> leftAdjust = true;
case '+' -> explicitPlus = true;
case ' ' -> space = true;
case '#' -> alternateForm = true;
case '0' -> zeroPad = true;
switch (c = p < n ? format.charAt(p++) : 0) {
case '-' -> flags |= LEFT_ADJUST;
case '+' -> flags |= EXPLICIT_PLUS;
case ' ' -> flags |= SPACE;
case '#' -> flags |= ALTERNATE_FORM;
case '0' -> flags |= ZERO_PAD;
default -> moreFlags = false;
}
}
Expand All @@ -69,39 +78,64 @@ public class FormatDesc {
throw new LuaError("invalid format (repeated flags)");
}

width = -1;
int width = -1;
if (CharProperties.isDigit(c)) {
width = c - '0';
c = ((p < n) ? strfrmt.charAt(p++) : 0);
c = p < n ? format.charAt(p++) : 0;
if (CharProperties.isDigit(c)) {
width = width * 10 + (c - '0');
c = ((p < n) ? strfrmt.charAt(p++) : 0);
width = width * 10 + c - '0';
c = p < n ? format.charAt(p++) : 0;
}
}
this.width = width;

precision = -1;
int precision = -1;
if (c == '.') {
c = ((p < n) ? strfrmt.charAt(p++) : 0);
c = p < n ? format.charAt(p++) : 0;
if (CharProperties.isDigit(c)) {
precision = c - '0';
c = ((p < n) ? strfrmt.charAt(p++) : 0);
c = p < n ? format.charAt(p++) : 0;
if (CharProperties.isDigit(c)) {
precision = precision * 10 + (c - '0');
c = ((p < n) ? strfrmt.charAt(p++) : 0);
precision = precision * 10 + c - '0';
c = p < n ? format.charAt(p++) : 0;
}
} else {
precision = 0;
}
flags |= PRECISION;
}
this.precision = precision;
this.flags = flags;

if (CharProperties.isDigit(c)) throw new LuaError("invalid format (width or precision too long)");

zeroPad &= !leftAdjust; // '-' overrides '0'
space &= !explicitPlus;
conversion = c;
length = p - start;
}

private boolean leftAdjust() {
return (flags & LEFT_ADJUST) != 0;
}

private boolean alternateForm() {
return (flags & ALTERNATE_FORM) != 0;
}

private boolean zeroPad() {
// Only set '0' if '0' is present and '-' is not.
return (flags & (ZERO_PAD | LEFT_ADJUST)) == ZERO_PAD;
}

private boolean explicitPlus() {
// Only set ' ' if ' ' is present and '+' is not.
return (flags & EXPLICIT_PLUS) != 0;
}

private boolean space() {
// Only set ' ' if ' ' is present and '+' is not.
return (flags & (SPACE | EXPLICIT_PLUS)) == SPACE;
}

public static FormatDesc ofUnsafe(String format) {
try {
return new FormatDesc(LuaString.valueOf(format), 0);
Expand All @@ -110,7 +144,17 @@ public static FormatDesc ofUnsafe(String format) {
}
}

public void format(Buffer buf, byte c) {
void checkFlags(int flags) throws LuaError {
if ((this.flags & ~flags) == 0) return;

var buffer = new Buffer();
buffer.append("invalid conversion specification: '%");
buffer.append(format, start, length);
buffer.append("'");
throw new LuaError(buffer.toLuaString());
}

void format(Buffer buf, byte c) {
buf.append(c);
}

Expand All @@ -122,16 +166,7 @@ public void format(Buffer buf, long number) {
case 'x' -> digits = Long.toHexString(number);
case 'X' -> digits = Long.toHexString(number).toUpperCase();
case 'o' -> digits = Long.toOctalString(number);
case 'u' -> {
// In order to remain safe with Java 8 we inline Long.toUnsignedString
if (number >= 0) {
digits = Long.toString(number);
} else {
long quot = (number >>> 1) / 5;
long rem = number - quot * 10;
digits = Long.toString(quot) + rem;
}
}
case 'u' -> digits = Long.toUnsignedString(number);
default -> {
digits = Long.toString(number);
hasSign = true;
Expand All @@ -141,7 +176,7 @@ public void format(Buffer buf, long number) {
if (number == 0) {
// "%.0d" and "%.0o" will be "".
// "%#.0d" will be "", but "%#.0o" will be "0".
if (precision == 0 && (conversion != 'o' || !alternateForm)) digits = "";
if (precision == 0 && (conversion != 'o' || !alternateForm())) digits = "";
}

int minWidth = digits.length();
Expand All @@ -152,13 +187,13 @@ public void format(Buffer buf, long number) {
if (number < 0) {
nDigits--;
digits = digits.substring(1);
} else if (explicitPlus || space) {
} else if (explicitPlus() || space()) {
minWidth++;
}
}

String prefix = "";
if (number != 0 && alternateForm) {
if (number != 0 && alternateForm()) {
// If we're not 0 and we've some alternative form, then prefix with that.
// Note that octal's "0" counts as a digit but hex's "0x" does not.
switch (conversion) {
Expand All @@ -175,7 +210,7 @@ public void format(Buffer buf, long number) {

if (precision > nDigits) {
nZeros = precision - nDigits;
} else if (precision == -1 && zeroPad && width > minWidth) {
} else if (precision == -1 && zeroPad() && width > minWidth) {
nZeros = width - minWidth;
} else {
nZeros = 0;
Expand All @@ -184,14 +219,14 @@ public void format(Buffer buf, long number) {
minWidth += nZeros;
int nSpaces = width > minWidth ? width - minWidth : 0;

if (!leftAdjust) pad(buf, ' ', nSpaces);
if (!leftAdjust()) pad(buf, ' ', nSpaces);

if (hasSign) {
if (number < 0) {
buf.append('-');
} else if (explicitPlus) {
} else if (explicitPlus()) {
buf.append('+');
} else if (space) {
} else if (space()) {
buf.append(' ');
}
}
Expand All @@ -200,7 +235,7 @@ public void format(Buffer buf, long number) {
if (nZeros > 0) pad(buf, '0', nZeros);
buf.append(digits);

if (leftAdjust) pad(buf, ' ', nSpaces);
if (leftAdjust()) pad(buf, ' ', nSpaces);
}

public void format(Buffer buf, double number) {
Expand All @@ -217,22 +252,27 @@ public void format(Buffer buf, double number) {
}
}

public void format(Buffer buf, LuaString s) {
int nullindex = s.indexOf((byte) '\0');
if (nullindex != -1) {
s = s.substringOfEnd(0, nullindex);
void format(Buffer buf, LuaString s) {
if (precision == -1 && s.length() >= 100) {
buf.append(s);
return;
}

int nullIndex = s.indexOf((byte) '\0');
if (nullIndex != -1) {
s = s.substringOfEnd(0, nullIndex);
}
if (precision >= 0 && s.length() > precision) {
s = s.substringOfEnd(0, precision);
}

int minwidth = s.length();
int nspaces = width > minwidth ? width - minwidth : 0;
if (!leftAdjust) pad(buf, ' ', nspaces);
int minWidth = s.length();
int nSpaces = width > minWidth ? width - minWidth : 0;
if (!leftAdjust()) pad(buf, ' ', nSpaces);

buf.append(s);

if (leftAdjust) pad(buf, ' ', nspaces);
if (leftAdjust()) pad(buf, ' ', nSpaces);
}

private static void pad(Buffer buf, char c, int n) {
Expand All @@ -243,12 +283,7 @@ private static void pad(Buffer buf, char c, int n) {
private DoubleToStringConverter.FormatOptions doubleOpts(boolean caps) {
return new DoubleToStringConverter.FormatOptions(
caps ? UPPER_SYMBOLS : LOWER_SYMBOLS,
explicitPlus,
space,
alternateForm,
width,
zeroPad,
leftAdjust
explicitPlus(), space(), alternateForm(), width, zeroPad(), leftAdjust()
);
}
}
Loading

0 comments on commit c2c0275

Please sign in to comment.