Skip to content
Open
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
58 changes: 50 additions & 8 deletions core/src/main/java/lucee/transformer/bytecode/PageImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ public final class PageImpl extends BodyBase implements Page {
// Maximum UDFs per constructor helper method to avoid JVM's 64KB method bytecode limit, chosen value is a safe estimate, size depends on CFC methdod signatures
private static final int MAX_UDF_PER_CONSTRUCTOR_METHOD = 30;

// Maximum keys per <cinit> helper method to avoid JVM's 64KB method bytecode limit, each key generates ~30 bytes
private static final int MAX_KEYS_PER_CINIT_METHOD = 1000;

public static final Type NULL = Type.getType(lucee.runtime.type.Null.class);
public static final Type KEY_IMPL = Type.getType(KeyImpl.class);
public static final Type KEY_CONSTANTS = Type.getType(KeyConstants.class);
Expand Down Expand Up @@ -1394,16 +1397,43 @@ else if (defaultExpr instanceof LitBooleanImpl) {
ga.push(keys.size()); // Array size
ga.newArray(Types.COLLECTION_KEY);

int index = 0;
for (LitString ls: keys.keySet()) {
ga.dup();
ga.push(index++);
ga.push(ls.getString());
// Split into helper methods if there are enough keys to risk MethodTooLargeException (LDEV-6134)
List<LitString> keyList = new ArrayList<>(keys.keySet());

if (keyList.size() > MAX_KEYS_PER_CINIT_METHOD) {
int batchNum = 0;
for (int batchStart = 0; batchStart < keyList.size(); batchStart += MAX_KEYS_PER_CINIT_METHOD) {
int batchEnd = Math.min(batchStart + MAX_KEYS_PER_CINIT_METHOD, keyList.size());
String helperMethodName = ASMUtil.createOverfowMethod("_cinitKeys", batchNum++);
Method helperMethod = new Method(helperMethodName, Type.VOID_TYPE, new Type[] { Types.COLLECTION_KEY_ARRAY });
GeneratorAdapter helperAdapter = new GeneratorAdapter(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, helperMethod, null, null, cw);

// call helper from <cinit>
ga.dup();
ga.invokeStatic(Type.getObjectType(name), helperMethod);

// ExpressionUtil.writeOutSilent(ls, bc, Expression.MODE_REF);
ga.invokeStatic(KEY_IMPL, KEY_INIT_KEYS);
ga.arrayStore(Types.COLLECTION_KEY);
// write batch into helper method
writeKeysBatch(helperAdapter, keyList, batchStart, batchEnd);

helperAdapter.returnValue();
helperAdapter.endMethod();
}
}
else {
// Small number of keys, inline directly in <cinit>
int index = 0;
for (LitString ls: keyList) {
ga.dup();
ga.push(index++);
ga.push(ls.getString());

// ExpressionUtil.writeOutSilent(ls, bc, Expression.MODE_REF);
ga.invokeStatic(KEY_IMPL, KEY_INIT_KEYS);
ga.arrayStore(Types.COLLECTION_KEY);
}
}

ga.putStatic(Type.getObjectType(name), "keys", Types.COLLECTION_KEY_ARRAY);

ga.returnValue();
Expand Down Expand Up @@ -1521,6 +1551,18 @@ private String getTagAttributeValue(Tag tag, String attrName) {
return null;
}

private static void writeKeysBatch(GeneratorAdapter ga, List<LitString> keyList, int from, int to) {
for (int i = from; i < to; i++) {
ga.loadArg(0); // CollectionKey[] array
ga.push(i);
ga.push(keyList.get(i).getString());

// ExpressionUtil.writeOutSilent(ls, bc, Expression.MODE_REF);
ga.invokeStatic(KEY_IMPL, KEY_INIT_KEYS);
ga.arrayStore(Types.COLLECTION_KEY);
}
}

private void writeOutStaticConstructor(ConstrBytecodeContext constr, Map<LitString, Integer> keys, ClassWriter cw, TagCIObject component, String name)
throws TransformerException {

Expand Down
50 changes: 50 additions & 0 deletions test/tickets/LDEV6134.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
component extends="org.lucee.cfml.test.LuceeTestCase" labels="bytecode" {

function beforeAll() {
generateFixture();
}

function run( testResults, testBox ) {
describe( "LDEV-6134 - MethodTooLargeException in <cinit> when CFC has thousands of unique string keys", function() {
it( title="component with 3000 keys compiles and instantiates without MethodTooLargeException", body=function( currentSpec ) {
var obj = new LDEV6134.CfcWithExcessStaticObjectKeys( parent={} );
expect( obj ).notToBeNull();
expect( obj.testWord( "word_000001" ) ).toBe( "word_000001" );
});
});
}

private string function getFixtureDir() {
return getDirectoryFromPath( getCurrentTemplatePath() ) & "LDEV6134/";
}

private string function getFixturePath() {
return getFixtureDir() & "CfcWithExcessStaticObjectKeys.cfc";
}

private void function generateFixture() {
var dir = getFixtureDir();
if ( !directoryExists( dir ) ) directoryCreate( dir );
var path = getFixturePath();
// cleanup any stale fixture from a previous run
if ( fileExists( path ) ) fileDelete( path );

var keyCount = 3000;
var nl = chr( 10 );
var cfml = 'component {#nl##nl#';
cfml &= ' function init( parent ) {#nl#';
cfml &= ' return this;#nl#';
cfml &= ' }#nl##nl#';
cfml &= ' string function testWord( required string word ) {#nl#';
cfml &= ' var words = {};#nl#';
loop from="1" to="#keyCount#" index="local.i" {
var key = "word_#numberFormat( i, '000000' )#";
cfml &= ' words["#key#"] = "#key#";#nl#';
}
cfml &= ' return words[ARGUMENTS.word];#nl#';
cfml &= ' }#nl#';
cfml &= '}#nl#';
fileWrite( path, cfml );
}

}
1 change: 1 addition & 0 deletions test/tickets/LDEV6134/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CfcWithExcessStaticObjectKeys.cfc
Loading