1717 */
1818package org .apache .drill .exec .compile ;
1919
20- import java . io . IOException ;
21- import java . util . LinkedList ;
22- import java . util . Map ;
23- import java . util . Set ;
24-
20+ import com . google . common . annotations . VisibleForTesting ;
21+ import com . google . common . base . Preconditions ;
22+ import com . google . common . collect . Lists ;
23+ import com . google . common . collect . Maps ;
24+ import com . google . common . collect . Sets ;
2525import org .apache .commons .lang3 .tuple .Pair ;
2626import org .apache .drill .common .config .DrillConfig ;
2727import org .apache .drill .common .util .DrillFileUtils ;
3535import org .objectweb .asm .ClassReader ;
3636import org .objectweb .asm .tree .ClassNode ;
3737
38- import com .google .common .annotations .VisibleForTesting ;
39- import com .google .common .base .Preconditions ;
40- import com .google .common .collect .Lists ;
41- import com .google .common .collect .Maps ;
42- import com .google .common .collect .Sets ;
38+ import java .io .IOException ;
39+ import java .util .LinkedList ;
40+ import java .util .Map ;
41+ import java .util .Set ;
4342
4443/**
4544 * Compiles generated code, merges the resulting class with the
5251public class ClassTransformer {
5352 private static final org .slf4j .Logger logger = org .slf4j .LoggerFactory .getLogger (ClassTransformer .class );
5453
55- private static final int MAX_SCALAR_REPLACE_CODE_SIZE = 2 * 1024 * 1024 ; // 2meg
54+ private static final int MAX_SCALAR_REPLACE_CODE_SIZE = 2 * 1024 * 1024 ; // 2meg
5655
5756 private final ByteCodeLoader byteCodeLoader = new ByteCodeLoader ();
5857 private final DrillConfig config ;
@@ -72,7 +71,7 @@ public enum ScalarReplacementOption {
7271 * @throws IllegalArgumentException if the string doesn't match any of the enum values
7372 */
7473 public static ScalarReplacementOption fromString (final String s ) {
75- switch (s ) {
74+ switch (s ) {
7675 case "off" :
7776 return OFF ;
7877 case "try" :
@@ -228,8 +227,11 @@ public Class<?> getImplementationClass(
228227 final TemplateClassDefinition <?> templateDefinition ,
229228 final String entireClass ,
230229 final String materializedClassName ) throws ClassTransformationException {
231- // unfortunately, this hasn't been set up at construction time, so we have to do it here
232- final ScalarReplacementOption scalarReplacementOption = ScalarReplacementOption .fromString (optionManager .getOption (ExecConstants .SCALAR_REPLACEMENT_VALIDATOR ));
230+ final ScalarReplacementOption scalarReplacementOption =
231+ ScalarReplacementOption .fromString (optionManager .getOption (ExecConstants .SCALAR_REPLACEMENT_VALIDATOR ));
232+
233+ // Track injected class names to avoid duplicates
234+ Set <String > injectedClassNames = new java .util .HashSet <>();
233235
234236 try {
235237 final long t1 = System .nanoTime ();
@@ -251,53 +253,32 @@ public Class<?> getImplementationClass(
251253 final Set <ClassSet > namesCompleted = Sets .newHashSet ();
252254 names .add (set );
253255
254- while ( !names .isEmpty () ) {
256+ while (!names .isEmpty ()) {
255257 final ClassSet nextSet = names .removeFirst ();
256258 if (namesCompleted .contains (nextSet )) {
257259 continue ;
258260 }
259261 final ClassNames nextPrecompiled = nextSet .precompiled ;
260262 final byte [] precompiledBytes = byteCodeLoader .getClassByteCodeFromPath (nextPrecompiled .clazz );
261263 final ClassNames nextGenerated = nextSet .generated ;
262- // keeps only classes that have not be merged
264+ // keeps only classes that have not been merged
263265 Pair <byte [], ClassNode > classNodePair = classesToMerge .remove (nextGenerated .slash );
264- final ClassNode generatedNode ;
265- if (classNodePair != null ) {
266- generatedNode = classNodePair .getValue ();
267- } else {
268- generatedNode = null ;
269- }
266+ final ClassNode generatedNode = (classNodePair != null ) ? classNodePair .getValue () : null ;
270267
271- /*
272- * TODO
273- * We're having a problem with some cases of scalar replacement, but we want to get
274- * the code in so it doesn't rot anymore.
275- *
276- * Here, we use the specified replacement option. The loop will allow us to retry if
277- * we're using TRY.
278- */
279268 MergedClassResult result = null ;
280- boolean scalarReplace = scalarReplacementOption != ScalarReplacementOption .OFF && entireClass .length () < MAX_SCALAR_REPLACE_CODE_SIZE ;
281- while (true ) {
269+ boolean scalarReplace = scalarReplacementOption != ScalarReplacementOption .OFF
270+ && entireClass .length () < MAX_SCALAR_REPLACE_CODE_SIZE ;
271+ while (true ) {
282272 try {
283273 result = MergeAdapter .getMergedClass (nextSet , precompiledBytes , generatedNode , scalarReplace );
284274 break ;
285- } catch (RuntimeException e ) {
286- // if we had a problem without using scalar replacement, then rethrow
275+ } catch (RuntimeException e ) {
287276 if (!scalarReplace ) {
288277 throw e ;
289278 }
290-
291- // if we did try to use scalar replacement, decide if we need to retry or not
292279 if (scalarReplacementOption == ScalarReplacementOption .ON ) {
293- // option is forced on, so this is a hard error
294280 throw e ;
295281 }
296-
297- /*
298- * We tried to use scalar replacement, with the option to fall back to not using it.
299- * Log this failure before trying again without scalar replacement.
300- */
301282 logger .info ("scalar replacement failure (retrying)\n " , e );
302283 scalarReplace = false ;
303284 }
@@ -307,26 +288,38 @@ public Class<?> getImplementationClass(
307288 s = s .replace (DrillFileUtils .SEPARATOR_CHAR , '.' );
308289 names .add (nextSet .getChild (s ));
309290 }
310- classLoader .injectByteCode (nextGenerated .dot , result .bytes );
291+
292+ // Only inject bytecode if not already injected
293+ if (!injectedClassNames .contains (nextGenerated .dot )) {
294+ classLoader .injectByteCode (nextGenerated .dot , result .bytes );
295+ injectedClassNames .add (nextGenerated .dot );
296+ }
297+
311298 namesCompleted .add (nextSet );
312299 }
313300
314301 // adds byte code of the classes that have not been merged to make them accessible for outer class
315302 for (Map .Entry <String , Pair <byte [], ClassNode >> clazz : classesToMerge .entrySet ()) {
316- classLoader .injectByteCode (clazz .getKey ().replace (DrillFileUtils .SEPARATOR_CHAR , '.' ), clazz .getValue ().getKey ());
303+ String classNameDot = clazz .getKey ().replace (DrillFileUtils .SEPARATOR_CHAR , '.' );
304+ if (!injectedClassNames .contains (classNameDot )) {
305+ classLoader .injectByteCode (classNameDot , clazz .getValue ().getKey ());
306+ injectedClassNames .add (classNameDot );
307+ }
317308 }
309+
318310 Class <?> c = classLoader .findClass (set .generated .dot );
319311 if (templateDefinition .getExternalInterface ().isAssignableFrom (c )) {
320312 logger .debug ("Compiled and merged {}: bytecode size = {}, time = {} ms." ,
321- c .getSimpleName (),
322- DrillStringUtils .readable (totalBytecodeSize ),
323- (System .nanoTime () - t1 + 500_000 ) / 1_000_000 );
313+ c .getSimpleName (),
314+ DrillStringUtils .readable (totalBytecodeSize ),
315+ (System .nanoTime () - t1 + 500_000 ) / 1_000_000 );
324316 return c ;
325317 }
326318
327319 throw new ClassTransformationException ("The requested class did not implement the expected interface." );
328320 } catch (CompileException | IOException | ClassNotFoundException e ) {
329- throw new ClassTransformationException (String .format ("Failure generating transformation classes for value: \n %s" , entireClass ), e );
321+ throw new ClassTransformationException (
322+ String .format ("Failure generating transformation classes for value: \n %s" , entireClass ), e );
330323 }
331324 }
332325}
0 commit comments