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 @@ -4387,9 +4387,8 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
if (implicitU != 0) {
flags = RuntimeRegex.applyUnicodeStringsFeatureToModifiers(flags);
}
RegexQuoteMeta.setCallSiteWarningState(warningState);
RegexQuoteMeta.setCallSiteWarningBits(warningBitsIndex >= 0
? code.stringPool[warningBitsIndex] : null);
RegexQuoteMeta.setCallSiteWarning(warningState,
warningBitsIndex >= 0 ? code.stringPool[warningBitsIndex] : null);
registers[rd] = RuntimeRegex.getQuotedRegex(
registers[patternReg].scalar(), flags,
namedCharacterExpansions);
Expand Down Expand Up @@ -4419,9 +4418,8 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
if (implicitU != 0) {
flags = RuntimeRegex.applyUnicodeStringsFeatureToModifiers(flags);
}
RegexQuoteMeta.setCallSiteWarningState(warningState);
RegexQuoteMeta.setCallSiteWarningBits(warningBitsIndex >= 0
? code.stringPool[warningBitsIndex] : null);
RegexQuoteMeta.setCallSiteWarning(warningState,
warningBitsIndex >= 0 ? code.stringPool[warningBitsIndex] : null);
registers[rd] = RuntimeRegex.getQuotedRegex(
registers[patternReg].scalar(), flags, callsiteId,
namedCharacterExpansions);
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/org/perlonjava/backend/jvm/EmitRegex.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ private static void emitRegexWarningState(EmitterVisitor emitterVisitor, Operato
emitterVisitor.ctx.mv.visitInsn(suppressed ? Opcodes.ICONST_M1
: fatal ? Opcodes.ICONST_2
: enabled ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/regex/RegexQuoteMeta",
"setCallSiteWarningState", "(I)V", false);
Object warningBits = node.getAnnotation("regexWarningBits");
if (warningBits instanceof String bits) {
emitterVisitor.ctx.mv.visitLdcInsn(bits);
Expand All @@ -87,7 +84,7 @@ private static void emitRegexWarningState(EmitterVisitor emitterVisitor, Operato
}
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/regex/RegexQuoteMeta",
"setCallSiteWarningBits", "(Ljava/lang/String;)V", false);
"setCallSiteWarning", "(ILjava/lang/String;)V", false);
}

/** Stack: pattern -> pattern carrying this literal's immutable lexical results. */
Expand Down
90 changes: 83 additions & 7 deletions src/main/java/org/perlonjava/runtime/regex/JoniRegexPattern.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,28 @@ record DeferredPropertyFact(String name, String displayName,
private static final int INPUT_ENCODING_CACHE_ENTRIES = 512;
private static final int INPUT_ENCODING_CACHE_MAX_LENGTH = 8_192;
private static final int DYNAMIC_PATTERN_CACHE_ENTRIES = 128;

private static final int IDENTITY_ARRAY_POOL_SIZE = 64;
private static final int[][] IDENTITY_ARRAYS = buildIdentityArrayPool();

private static int[][] buildIdentityArrayPool() {
int[][] pool = new int[IDENTITY_ARRAY_POOL_SIZE + 1][];
for (int len = 0; len <= IDENTITY_ARRAY_POOL_SIZE; len++) {
int[] identity = new int[len + 1];
for (int i = 0; i <= len; i++) identity[i] = i;
pool[len] = identity;
}
return pool;
}

private static int[] identityArray(int length) {
if (length <= IDENTITY_ARRAY_POOL_SIZE) {
return IDENTITY_ARRAYS[length];
}
int[] identity = new int[length + 1];
for (int i = 0; i <= length; i++) identity[i] = i;
return identity;
}
private static final Map<String, InputEncoding> INPUT_ENCODINGS = inputEncodingCache();
private static final Map<String, InputEncoding> BYTE_INPUT_ENCODINGS = inputEncodingCache();
private static final Map<RuntimeScalar, SubjectInputEncodings> SUBJECT_INPUT_ENCODINGS =
Expand Down Expand Up @@ -324,6 +346,7 @@ private static CharacterPropertyResolver.Result resolveCharacterProperty(
private final boolean byteMode;
private final java.nio.charset.Charset sourceCharset;
private final List<String> compileWarnings;
private final String fixedLiteral;

JoniRegexPattern(String perlPattern, RegexFlags flags) {
this(perlPattern, flags, 0, false);
Expand Down Expand Up @@ -455,6 +478,27 @@ public boolean supportsPositions() {
NamedGroupMaps groupMaps = collectNamedGroups(regex);
namedGroups = groupMaps.logical();
physicalNamedGroups = groupMaps.physical();
fixedLiteral = detectFixedLiteral(sourcePattern, regex, flags, byteMode);
}

private static String detectFixedLiteral(String pattern, Regex compiled,
RegexFlags flags, boolean byteMode) {
if (compiled.numberOfCaptures() != 0 || byteMode) return null;
if (flags != null && (flags.isCaseInsensitive() || flags.isExtended())) return null;
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (c == '\\' || c == '.' || c == '^' || c == '$' || c == '*'
|| c == '+' || c == '?' || c == '{' || c == '}'
|| c == '[' || c == ']' || c == '(' || c == ')'
|| c == '|') {
return null;
}
}
return pattern.isEmpty() ? null : pattern;
}

String getFixedLiteral() {
return fixedLiteral;
}

private record WarningDisplay(String pattern, int offset) {}
Expand Down Expand Up @@ -655,7 +699,13 @@ static InputEncoding inputEncoding(String input, RuntimeScalar subject, boolean
}

static InputEncoding inputEncoding(String input) {
if (input.length() > INPUT_ENCODING_CACHE_MAX_LENGTH) {
int len = input.length();
if (len <= IDENTITY_ARRAY_POOL_SIZE && isAscii(input, len)) {
byte[] bytes = asciiToBytes(input, len);
int[] identity = IDENTITY_ARRAYS[len];
return new InputEncoding(bytes, identity, identity);
}
if (len > INPUT_ENCODING_CACHE_MAX_LENGTH) {
return buildInputEncoding(input);
}
synchronized (INPUT_ENCODINGS) {
Expand All @@ -664,7 +714,14 @@ static InputEncoding inputEncoding(String input) {
}

static InputEncoding byteInputEncoding(String input) {
if (input.length() > INPUT_ENCODING_CACHE_MAX_LENGTH) {
int len = input.length();
if (len <= IDENTITY_ARRAY_POOL_SIZE) {
byte[] bytes = new byte[len];
for (int i = 0; i < len; i++) bytes[i] = (byte) input.charAt(i);
int[] identity = IDENTITY_ARRAYS[len];
return new InputEncoding(bytes, identity, identity);
}
if (len > INPUT_ENCODING_CACHE_MAX_LENGTH) {
return buildByteInputEncoding(input);
}
synchronized (BYTE_INPUT_ENCODINGS) {
Expand All @@ -675,23 +732,42 @@ static InputEncoding byteInputEncoding(String input) {

private static InputEncoding buildByteInputEncoding(String input) {
byte[] bytes = input.getBytes(StandardCharsets.ISO_8859_1);
int[] identity = new int[input.length() + 1];
for (int i = 0; i < identity.length; i++) identity[i] = i;
int[] identity = identityArray(input.length());
return new InputEncoding(bytes, identity, identity);
}

private static InputEncoding buildInputEncoding(String input) {
int len = input.length();
if (isAscii(input, len)) {
byte[] bytes = asciiToBytes(input, len);
int[] identity = identityArray(len);
return new InputEncoding(bytes, identity, identity);
}
byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
if (bytes.length == input.length()) {
int[] identity = new int[input.length() + 1];
for (int i = 0; i <= input.length(); i++) identity[i] = i;
if (bytes.length == len) {
int[] identity = identityArray(len);
return new InputEncoding(bytes, identity, identity);
}
int[] charToByte = JoniRegexMatcher.buildCharToByte(input);
int[] byteToChar = JoniRegexMatcher.buildByteToChar(input, bytes.length, charToByte);
return new InputEncoding(bytes, charToByte, byteToChar);
}

private static boolean isAscii(String input, int len) {
for (int i = 0; i < len; i++) {
if (input.charAt(i) > 0x7f) return false;
}
return true;
}

private static byte[] asciiToBytes(String input, int len) {
byte[] bytes = new byte[len];
for (int i = 0; i < len; i++) {
bytes[i] = (byte) input.charAt(i);
}
return bytes;
}

String patternDescription() {
return sourcePattern;
}
Expand Down
38 changes: 26 additions & 12 deletions src/main/java/org/perlonjava/runtime/regex/RegexQuoteMeta.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@

public class RegexQuoteMeta {
private static final int CALL_SITE_WARNING_UNSET = Integer.MIN_VALUE;
private static final ThreadLocal<int[]> CALL_SITE_WARNING_STATE = ThreadLocal.withInitial(() -> new int[]{CALL_SITE_WARNING_UNSET});
private static final ThreadLocal<String[]> CALL_SITE_WARNING_BITS = ThreadLocal.withInitial(() -> new String[]{null});

static final class CallSiteWarningHolder {
int state = CALL_SITE_WARNING_UNSET;
String bits;
}

private static final ThreadLocal<CallSiteWarningHolder> CALL_SITE_WARNING =
ThreadLocal.withInitial(CallSiteWarningHolder::new);
private static final ThreadLocal<String> PARSER_WARNING_BITS = new ThreadLocal<>();
private static final ThreadLocal<String> MATCH_TARGET_NAME = new ThreadLocal<>();

Expand All @@ -17,21 +23,27 @@ public record ConstructionWarningDisposition(boolean enabled, boolean fatal) {
}

public static void setCallSiteWarningState(int state) {
int[] holder = CALL_SITE_WARNING_STATE.get();
if (holder[0] != state) holder[0] = state;
CallSiteWarningHolder h = CALL_SITE_WARNING.get();
if (h.state != state) h.state = state;
}

public static void setCallSiteWarningBits(String bits) {
String[] holder = CALL_SITE_WARNING_BITS.get();
if (holder[0] != bits) holder[0] = bits;
CallSiteWarningHolder h = CALL_SITE_WARNING.get();
if (h.bits != bits) h.bits = bits;
}

public static void setCallSiteWarning(int state, String bits) {
CallSiteWarningHolder h = CALL_SITE_WARNING.get();
if (h.state != state) h.state = state;
if (h.bits != bits) h.bits = bits;
}

public static String getCallSiteWarningBits() {
return CALL_SITE_WARNING_BITS.get()[0];
return CALL_SITE_WARNING.get().bits;
}

public static boolean isCallSiteWarningExplicitlyDisabled() {
int state = CALL_SITE_WARNING_STATE.get()[0];
int state = CALL_SITE_WARNING.get().state;
return state != CALL_SITE_WARNING_UNSET && state < 0;
}

Expand Down Expand Up @@ -61,10 +73,11 @@ static void warnAtConstruction(String message, boolean strictDefault) {
// This warning belongs to construction of the interpolated pattern.
// Retaining it on the cached RuntimeRegex re-emits it for every match
// and can leak a warning-enabled compilation into a no-warnings use.
int state = CALL_SITE_WARNING_STATE.get()[0];
CallSiteWarningHolder h = CALL_SITE_WARNING.get();
int state = h.state;
RuntimeScalar warning = new RuntimeScalar(message);
RuntimeScalar where = new RuntimeScalar("");
String warningBits = CALL_SITE_WARNING_BITS.get()[0];
String warningBits = h.bits;
if (warningBits == null) {
// Synthetic/interpolated regex wrappers do not always have a
// dedicated quote opcode. Use the active statement mask rather
Expand Down Expand Up @@ -93,7 +106,8 @@ static void warnAtConstruction(String message, boolean strictDefault) {
public static ConstructionWarningDisposition constructionWarningDisposition(
String message, boolean strictDefault) {
String category = warningCategory(message);
String warningBits = CALL_SITE_WARNING_BITS.get()[0];
CallSiteWarningHolder h = CALL_SITE_WARNING.get();
String warningBits = h.bits;
if (warningBits == null) {
warningBits = WarningBitsRegistry.getRuntimeWarningBits();
}
Expand All @@ -108,7 +122,7 @@ public static ConstructionWarningDisposition constructionWarningDisposition(
enabled && WarningFlags.isFatalInBits(warningBits, category));
}

int state = CALL_SITE_WARNING_STATE.get()[0];
int state = h.state;
if (state != CALL_SITE_WARNING_UNSET) {
return new ConstructionWarningDisposition(state > 0, state == 2);
}
Expand Down
Loading
Loading