Skip to content

Commit 2c8c0c7

Browse files
committed
Add bound checking to accelerated intrinsics
Prior to JDK25, due to a race condition in AbstractStringBuilder, it is possible for some accelerated String intrinsics to perform an out of bounds array access. This applies to: java/lang/StringLatin1.inflate([BI[CII)V java/lang/StringLatin1.indexOf([BI[BII)I java/lang/StringUTF16.indexOf([BI[BII)I To address this, bound checking has been added such that the safe slow path code is performed in the rares cases that an out of bounds array access may occur. Former behavior is restored via the TR_DisableStringIntrinsicFlagChk envvar. As the problem does not exist in JDK25, the bound checking is not added in JDK25 and onward. Issue: #23880 Signed-off-by: jimmyk <jimmyk@ca.ibm.com>
1 parent 19c14ce commit 2c8c0c7

7 files changed

Lines changed: 381 additions & 0 deletions

File tree

runtime/compiler/aarch64/codegen/J9TreeEvaluator.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6954,6 +6954,8 @@ static TR::Register *inlineIntrinsicStringIndexOfString(TR::Node *node, TR::Code
69546954
doneLabel->setEndInternalControlFlow();
69556955

69566956
generateLabelInstruction(cg, TR::InstOpCode::label, node, startLabel);
6957+
cg->generateDebugCounter(
6958+
TR::DebugCounter::debugCounterName(cg->comp(), "inlineIntrinsicIndexOf/(%s)", cg->comp()->signature()));
69576959

69586960
const int32_t vecWidth = 16;
69596961
const int32_t shift = isLatin1 ? 0 : 1;
@@ -7254,6 +7256,9 @@ static TR::Register *inlineStringLatin1Inflate(TR::Node *node, TR::CodeGenerator
72547256
TR::LabelSymbol *doneLabel = generateLabelSymbol(cg);
72557257
TR_Debug *debugObj = cg->getDebug();
72567258

7259+
cg->generateDebugCounter(
7260+
TR::DebugCounter::debugCounterName(cg->comp(), "inlineIntrinsicInflate/(%s)", cg->comp()->signature()));
7261+
72577262
auto branchToDoneIfZeroInstr
72587263
= generateCompareBranchInstruction(cg, TR::InstOpCode::cbzw, node, savedLengthReg, doneLabel);
72597264
if (debugObj) {
@@ -7640,6 +7645,7 @@ bool J9::ARM64::CodeGenerator::inlineDirectCall(TR::Node *node, TR::Register *&r
76407645
TR::Compilation *comp = cg->comp();
76417646
TR::MethodSymbol *methodSymbol = node->getSymbol()->getMethodSymbol();
76427647
static const bool disableCRC32 = feGetEnv("TR_aarch64DisableCRC32") != NULL;
7648+
static const bool disableStringIntrinsicFlagChk = (feGetEnv("TR_DisableStringIntrinsicFlagChk") != NULL);
76437649

76447650
if (OMR::CodeGeneratorConnector::inlineDirectCall(node, resultReg)) {
76457651
return true;
@@ -7663,6 +7669,12 @@ bool J9::ARM64::CodeGenerator::inlineDirectCall(TR::Node *node, TR::Register *&r
76637669
break;
76647670

76657671
case TR::java_lang_StringLatin1_indexOf:
7672+
#if JAVA_SPEC_VERSION < 25
7673+
if (!disableStringIntrinsicFlagChk && !node->isSafeForCGToInlineStringIntrinsic()) {
7674+
break;
7675+
}
7676+
/* Intentional fallthrough. */
7677+
#endif /* JAVA_SPEC_VERSION < 25 */
76667678
case TR::com_ibm_jit_JITHelpers_intrinsicIndexOfStringLatin1:
76677679
if (cg->getSupportsInlineStringIndexOfString()) {
76687680
resultReg = inlineIntrinsicStringIndexOfString(node, cg, true);
@@ -7671,6 +7683,12 @@ bool J9::ARM64::CodeGenerator::inlineDirectCall(TR::Node *node, TR::Register *&r
76717683
break;
76727684

76737685
case TR::java_lang_StringUTF16_indexOf:
7686+
#if JAVA_SPEC_VERSION < 25
7687+
if (!disableStringIntrinsicFlagChk && !node->isSafeForCGToInlineStringIntrinsic()) {
7688+
break;
7689+
}
7690+
/* Intentional fallthrough. */
7691+
#endif /* JAVA_SPEC_VERSION < 25 */
76747692
case TR::java_lang_StringUTF16_indexOfUnsafe:
76757693
case TR::com_ibm_jit_JITHelpers_intrinsicIndexOfStringUTF16:
76767694
if (cg->getSupportsInlineStringIndexOfString()) {
@@ -7783,6 +7801,11 @@ bool J9::ARM64::CodeGenerator::inlineDirectCall(TR::Node *node, TR::Register *&r
77837801

77847802
case TR::java_lang_StringLatin1_inflate_BICII:
77857803
if (cg->getSupportsInlineStringLatin1Inflate()) {
7804+
#if JAVA_SPEC_VERSION < 25
7805+
if (!disableStringIntrinsicFlagChk && !node->isSafeForCGToInlineStringIntrinsic()) {
7806+
break;
7807+
}
7808+
#endif /* JAVA_SPEC_VERSION < 25 */
77867809
resultReg = inlineStringLatin1Inflate(node, cg);
77877810
return true;
77887811
}

runtime/compiler/optimizer/J9RecognizedCallTransformer.cpp

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,221 @@ void J9::RecognizedCallTransformer::process_java_lang_StringCoding_encodeASCII(T
279279
cfg->removeEdge(fallthroughBlock, fallbackPathBlock);
280280
}
281281

282+
void J9::RecognizedCallTransformer::process_java_lang_StringLatin1_inflate_BICII(TR::TreeTop *treetop, TR::Node *node)
283+
{
284+
/*
285+
* Prior to JDK25, it is possible for an AbstractStringBuilder race condition to trigger a call to
286+
* java/lang/StringLatin1.inflate([BI[CII)V in such a way that an out of bounds exception gets triggered. This
287+
* transformation adds a check for that possibility and calls the slow path if an exception needs to be thrown in
288+
* the future.
289+
*/
290+
TransformUtil::createTempsForCall(this, treetop);
291+
292+
/* Input byte array containing the string to inflate. */
293+
TR::Node *srcObjNode = node->getFirstChild();
294+
295+
/* Offset into the input byte array. Unit is number of bytes. */
296+
TR::Node *srcOffNode = node->getSecondChild();
297+
298+
/* Output char array to be modified with the result. */
299+
TR::Node *dstObjNode = node->getChild(2);
300+
301+
/* Offset into the output char array. Unit is number of chars. */
302+
TR::Node *dstOffNode = node->getChild(3);
303+
304+
/* Number of bytes to read from source and also number of chars to write to destination. */
305+
TR::Node *copyLenNode = node->getChild(4);
306+
307+
TR::Node *srcArrayLenNode = TR::Node::create(TR::arraylength, 1, srcObjNode->duplicateTree());
308+
srcArrayLenNode->setIsNonNegative(true);
309+
310+
TR::Node *dstArrayLenNode = TR::Node::create(TR::arraylength, 1, dstObjNode->duplicateTree());
311+
dstArrayLenNode->setIsNonNegative(true);
312+
313+
/*
314+
* The call node is duplicated to set up a fast path and a slow path.
315+
* The fast path has the inlineStringIntrinsic flag set so that it gets transformed by inlineDirectCall.
316+
* The slow path has the skipRecognizedCallTransformation flag set so it doesn't get transformed and
317+
* RecognizedCallTransformer won't look at it again.
318+
*/
319+
TR::TreeTop *slowPathTreeTop = TR::TreeTop::create(comp(), treetop->getNode()->duplicateTree());
320+
slowPathTreeTop->getNode()->getFirstChild()->setSkipRecognizedCallTransformation(true);
321+
322+
TR::TreeTop *fastPathTreeTop = TR::TreeTop::create(comp(), treetop->getNode()->duplicateTree());
323+
fastPathTreeTop->getNode()->getFirstChild()->setIsSafeForCGToInlineStringIntrinsic(true);
324+
325+
/*
326+
* Add the following two checks to confirm that src and dst arrays are big enough:
327+
* srcArray.length >= (srcOffNode+copyLenNode)
328+
* dstArray.length >= (dstOffNode+copyLenNode)
329+
* If either is false, an out of bounds exception will occur so the code must branch to the slow path.
330+
*/
331+
TR::Node *srcOffsetPlusLenNode
332+
= TR::Node::create(TR::iadd, 2, srcOffNode->duplicateTree(), copyLenNode->duplicateTree());
333+
TR::Node *dstOffsetPlusLenNode
334+
= TR::Node::create(TR::iadd, 2, dstOffNode->duplicateTree(), copyLenNode->duplicateTree());
335+
336+
TR::Node *srcCmpNode = TR::Node::createif(TR::ificmplt, srcArrayLenNode, srcOffsetPlusLenNode, NULL);
337+
TR::TreeTop *srcCmpTree = TR::TreeTop::create(comp(), srcCmpNode);
338+
339+
TR::Node *dstCmpNode = TR::Node::createif(TR::ificmplt, dstArrayLenNode, dstOffsetPlusLenNode, NULL);
340+
TR::TreeTop *dstCmpTree = TR::TreeTop::create(comp(), dstCmpNode);
341+
342+
TR::Block *callBlock = treetop->getEnclosingBlock();
343+
344+
/*
345+
* createConditionalBlocksBeforeTree is used to create the source array length check.
346+
* treetop is the current call treetop and the split point.
347+
* srcCmpTree is the check to see if out of bounds access will occur in the src array.
348+
* slowPathTreeTop is the slow path. This is a cold block that is branched to if an OOB exception will happen.
349+
* fastPathTreeTop is the fast path. This is the fallthrough path.
350+
* The slow path and fast path then merge back into each other.
351+
*/
352+
callBlock->createConditionalBlocksBeforeTree(treetop, srcCmpTree, slowPathTreeTop, fastPathTreeTop,
353+
comp()->getFlowGraph(), false, true);
354+
355+
/*
356+
* dstCmpTree is the check to see if out of bounds access will occur in the dst array.
357+
* The check is placed after the src out of bounds access check.
358+
* If the dst array is too small, the code branches to the slow path to handle the OOB exception.
359+
*/
360+
TR::Block *dstCmpBlock = TR::Block::createEmptyBlock(srcCmpNode, comp(), 0, NULL);
361+
dstCmpBlock->setFrequency(callBlock->getFrequency());
362+
comp()->getFlowGraph()->addNode(dstCmpBlock);
363+
364+
callBlock->getExit()->join(dstCmpBlock->getEntry());
365+
dstCmpBlock->getExit()->join(fastPathTreeTop->getEnclosingBlock()->getEntry());
366+
comp()->getFlowGraph()->copyExceptionSuccessors(callBlock, dstCmpBlock);
367+
368+
dstCmpBlock->append(dstCmpTree);
369+
dstCmpNode->setBranchDestination(srcCmpNode->getBranchDestination());
370+
371+
comp()->getFlowGraph()->addEdge(TR::CFGEdge::createEdge(callBlock, dstCmpBlock, comp()->trMemory()));
372+
comp()->getFlowGraph()->addEdge(
373+
TR::CFGEdge::createEdge(dstCmpBlock, fastPathTreeTop->getEnclosingBlock(), comp()->trMemory()));
374+
comp()->getFlowGraph()->addEdge(
375+
TR::CFGEdge::createEdge(dstCmpBlock, slowPathTreeTop->getEnclosingBlock(), comp()->trMemory()));
376+
377+
comp()->getFlowGraph()->removeEdge(callBlock, fastPathTreeTop->getEnclosingBlock());
378+
379+
TR::DebugCounter::prependDebugCounter(comp(),
380+
TR::DebugCounter::debugCounterName(comp(), "treesIntrinsicLatin1Inflate/fast/(%s)", comp()->signature()),
381+
fastPathTreeTop);
382+
TR::DebugCounter::prependDebugCounter(comp(),
383+
TR::DebugCounter::debugCounterName(comp(), "treesIntrinsicLatin1Inflate/slow/(%s)", comp()->signature()),
384+
slowPathTreeTop);
385+
}
386+
387+
void J9::RecognizedCallTransformer::process_java_lang_StringUTF16_indexOf_BIBII(TR::TreeTop *treetop, TR::Node *node)
388+
{
389+
process_java_lang_StringLatin1_indexOf_BIBII(treetop, node, false /* isLatin1 */);
390+
}
391+
392+
void J9::RecognizedCallTransformer::process_java_lang_StringLatin1_indexOf_BIBII(TR::TreeTop *treetop, TR::Node *node,
393+
bool isLatin1)
394+
{
395+
/*
396+
* The indexOf operation is searching for an index of a substring inside of another string. Prior to JDK25, it is
397+
* possible for an AbstractStringBuilder race condition to trigger a call to indexOf in such a way that an out of
398+
* bounds exception gets triggered. This transformation checks for that possibility and calls the slow path for
399+
* safety if there is a chance it may occur.
400+
*/
401+
402+
TransformUtil::createTempsForCall(this, treetop);
403+
404+
/* This is the byte[] containing the characters for the string being searched in. */
405+
TR::Node *arrayObjNode = node->getFirstChild();
406+
407+
/*
408+
* This is the number of characters thought to be in the string being searched in. Due to the race condition, it
409+
* may be incorrect.
410+
*/
411+
TR::Node *arrayLenNode = node->getSecondChild();
412+
413+
/*
414+
* maxArrayLenNode represents the maxmimum number of bytes that can be in the String being searched in without going
415+
* out of bounds.
416+
*/
417+
TR::Node *maxArrayLenNode = TR::Node::create(TR::arraylength, 1, arrayObjNode->duplicateTree());
418+
maxArrayLenNode->setIsNonNegative(true);
419+
420+
/*
421+
* The call node is duplicated to set up a fast path and a slow path.
422+
* The fast path has the inlineStringIntrinsic flag set so that it gets transformed by inlineDirectCall.
423+
* The slow path has the skipRecognizedCallTransformation flag set so it doesn't get transformed and
424+
* RecognizedCallTransformer won't look at it again.
425+
*/
426+
TR::TreeTop *slowPathTreeTop = TR::TreeTop::create(comp(), treetop->getNode()->duplicateTree());
427+
slowPathTreeTop->getNode()->getFirstChild()->setSkipRecognizedCallTransformation(true);
428+
429+
TR::TreeTop *fastPathTreeTop = TR::TreeTop::create(comp(), treetop->getNode()->duplicateTree());
430+
fastPathTreeTop->getNode()->getFirstChild()->setIsSafeForCGToInlineStringIntrinsic(true);
431+
432+
TR::SymbolReference *newSymbolReference = NULL;
433+
TR::DataType dataType = node->getDataType();
434+
435+
/* If the call node has a reference count >1, a symref is created to hold the result. */
436+
if (node->getReferenceCount() > 1) {
437+
newSymbolReference = comp()->getSymRefTab()->createTemporary(comp()->getMethodSymbol(), dataType);
438+
TR::Node::recreate(node, comp()->il.opCodeForDirectLoad(dataType));
439+
node->setSymbolReference(newSymbolReference);
440+
node->removeAllChildren();
441+
}
442+
443+
/*
444+
* For latin1, the value of maxArrayLenNode must be greater than or equal to the value of arrayLenNode.
445+
* For UTF16, the value of (maxArrayLenNode >> 1) must be greater than or equal to the value of arrayLenNode.
446+
* If this isn't true, it is possible for the indexOf operation to go out of bounds so the slow path must be taken.
447+
*/
448+
if (!isLatin1) {
449+
maxArrayLenNode = TR::Node::create(TR::ishr, 2, maxArrayLenNode, TR::Node::create(TR::iconst, 0, 1));
450+
}
451+
TR::Node *cmpNode = TR::Node::createif(TR::ificmpgt, arrayLenNode->duplicateTree(), maxArrayLenNode, NULL);
452+
TR::TreeTop *cmpTree = TR::TreeTop::create(comp(), cmpNode);
453+
454+
TR::Block *callBlock = treetop->getEnclosingBlock();
455+
456+
/*
457+
* createConditionalBlocksBeforeTree puts everything together.
458+
* treetop is the current call treetop and the split point.
459+
* cmpTree is the check to see if out of bounds access is possible.
460+
* slowPathTreeTop is the slow path. This is a cold block that is branched to if OOB is possible.
461+
* fastPathTreeTop is the fast path. This is the fallthrough path.
462+
* The slow path and fast path then merge back into each other.
463+
*/
464+
callBlock->createConditionalBlocksBeforeTree(treetop, cmpTree, slowPathTreeTop, fastPathTreeTop,
465+
comp()->getFlowGraph(), false, true);
466+
467+
/* If the call node has a reference count >1, a symref is used to record the return value from the call. */
468+
if (newSymbolReference) {
469+
TR::Node *fastPathStoreNode = TR::Node::createWithSymRef(comp()->il.opCodeForDirectStore(dataType), 1, 1,
470+
fastPathTreeTop->getNode()->getFirstChild(), newSymbolReference);
471+
TR::TreeTop *fastPathStoreTree = TR::TreeTop::create(comp(), fastPathStoreNode);
472+
fastPathTreeTop->insertAfter(fastPathStoreTree);
473+
474+
TR::Node *slowPathStoreNode = TR::Node::createWithSymRef(comp()->il.opCodeForDirectStore(dataType), 1, 1,
475+
slowPathTreeTop->getNode()->getFirstChild(), newSymbolReference);
476+
TR::TreeTop *slowPathStoreTree = TR::TreeTop::create(comp(), slowPathStoreNode);
477+
slowPathTreeTop->insertAfter(slowPathStoreTree);
478+
}
479+
480+
if (isLatin1) {
481+
TR::DebugCounter::prependDebugCounter(comp(),
482+
TR::DebugCounter::debugCounterName(comp(), "treesIntrinsicLatin1IndexOf/fast/(%s)", comp()->signature()),
483+
fastPathTreeTop);
484+
TR::DebugCounter::prependDebugCounter(comp(),
485+
TR::DebugCounter::debugCounterName(comp(), "treesIntrinsicLatin1IndexOf/slow/(%s)", comp()->signature()),
486+
slowPathTreeTop);
487+
} else {
488+
TR::DebugCounter::prependDebugCounter(comp(),
489+
TR::DebugCounter::debugCounterName(comp(), "treesIntrinsicUTF16IndexOf/fast/(%s)", comp()->signature()),
490+
fastPathTreeTop);
491+
TR::DebugCounter::prependDebugCounter(comp(),
492+
TR::DebugCounter::debugCounterName(comp(), "treesIntrinsicUTF16IndexOf/slow/(%s)", comp()->signature()),
493+
slowPathTreeTop);
494+
}
495+
}
496+
282497
void J9::RecognizedCallTransformer::process_java_lang_StringLatin1_inflate_BIBII(TR::TreeTop *treetop, TR::Node *node)
283498
{
284499
/*
@@ -1796,6 +2011,7 @@ bool J9::RecognizedCallTransformer::isInlineable(TR::TreeTop *treetop)
17962011
TR::RecognizedMethod rm = node->getSymbol()->castToMethodSymbol()->getMandatoryRecognizedMethod();
17972012

17982013
bool isILGenPass = !getLastRun();
2014+
static const bool disableStringIntrinsicFlagChk = (feGetEnv("TR_DisableStringIntrinsicFlagChk") != NULL);
17992015
if (isILGenPass) {
18002016
switch (rm) {
18012017
case TR::sun_misc_Unsafe_getAndAddInt:
@@ -1875,6 +2091,19 @@ bool J9::RecognizedCallTransformer::isInlineable(TR::TreeTop *treetop)
18752091
return cg()->getSupportsInlineEncodeASCII();
18762092
case TR::java_lang_StringLatin1_inflate_BIBII:
18772093
return (cg()->getSupportsArrayTranslateTROTNoBreak() && !comp()->target().cpu.isPower());
2094+
#if JAVA_SPEC_VERSION < 25
2095+
case TR::java_lang_StringLatin1_inflate_BICII:
2096+
return (!disableStringIntrinsicFlagChk && cg()->getSupportsInlineStringLatin1Inflate()
2097+
&& !node->isSafeForCGToInlineStringIntrinsic() && !node->checkSkipRecognizedCallTransformation());
2098+
case TR::java_lang_StringUTF16_indexOf:
2099+
if (comp()->target().cpu.isPower()) {
2100+
return false;
2101+
}
2102+
/* Intentional fallthrough. */
2103+
case TR::java_lang_StringLatin1_indexOf:
2104+
return (!disableStringIntrinsicFlagChk && cg()->getSupportsInlineStringIndexOfString()
2105+
&& !node->isSafeForCGToInlineStringIntrinsic() && !node->checkSkipRecognizedCallTransformation());
2106+
#endif /* JAVA_SPEC_VERSION < 25 */
18782107
case TR::jdk_internal_util_ArraysSupport_vectorizedMismatch:
18792108
return cg()->getSupportsInlineVectorizedMismatch();
18802109
default:
@@ -2020,6 +2249,17 @@ void J9::RecognizedCallTransformer::transform(TR::TreeTop *treetop)
20202249
case TR::java_lang_StringLatin1_inflate_BIBII:
20212250
process_java_lang_StringLatin1_inflate_BIBII(treetop, node);
20222251
break;
2252+
#if JAVA_SPEC_VERSION < 25
2253+
case TR::java_lang_StringLatin1_inflate_BICII:
2254+
process_java_lang_StringLatin1_inflate_BICII(treetop, node);
2255+
break;
2256+
case TR::java_lang_StringUTF16_indexOf:
2257+
process_java_lang_StringUTF16_indexOf_BIBII(treetop, node);
2258+
break;
2259+
case TR::java_lang_StringLatin1_indexOf:
2260+
process_java_lang_StringLatin1_indexOf_BIBII(treetop, node, true);
2261+
break;
2262+
#endif /* JAVA_SPEC_VERSION < 25 */
20232263
case TR::java_lang_StrictMath_sqrt:
20242264
case TR::java_lang_Math_sqrt:
20252265
process_java_lang_StrictMath_and_Math_sqrt(treetop, node);

0 commit comments

Comments
 (0)