Skip to content

Commit bc63fb1

Browse files
committed
Fix: Bytecode DSL parser should detect bad root node overrides in parent classes; code generation should not delegate to super method if they are abstract.
1 parent e7aba15 commit bc63fb1

File tree

4 files changed

+213
-59
lines changed

4 files changed

+213
-59
lines changed

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/error_tests/ErrorTests.java

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ protected BadOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor)
367367
super(language, frameDescriptor);
368368
}
369369

370-
private static final String ERROR_MESSAGE_DELEGATED = "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final.";
370+
private static final String ERROR_MESSAGE_DELEGATED = "This method is overridden by the generated Bytecode DSL class. It cannot be declared final.";
371371
private static final String ERROR_MESSAGE = ERROR_MESSAGE_DELEGATED +
372372
" You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it.";
373373

@@ -389,7 +389,7 @@ public final int findBytecodeIndex(Node node, Frame frame) {
389389
return 0;
390390
}
391391

392-
@ExpectError(ERROR_MESSAGE)
392+
@ExpectError(ERROR_MESSAGE_DELEGATED)
393393
@Override
394394
public final Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex) {
395395
return null;
@@ -401,7 +401,7 @@ public final boolean isInstrumentable() {
401401
return false;
402402
}
403403

404-
@ExpectError(ERROR_MESSAGE)
404+
@ExpectError(ERROR_MESSAGE + " Bytecode DSL interpreters should use GenerateBytecode#captureFramesForTrace instead.")
405405
@Override
406406
public final boolean isCaptureFramesForTrace(boolean compiledFrame) {
407407
return false;
@@ -459,6 +459,11 @@ public final BytecodeRootNodes<?> getRootNodes() {
459459
* methods. The generated code should respect the wider visibility (otherwise, a compiler error
460460
* will occur).
461461
*/
462+
@ExpectWarning({
463+
"Method isInstrumentable() in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithOverrides is overridden by the generated Bytecode DSL class." +
464+
" You can suppress this warning by re-declaring the method as abstract in this class.",
465+
"Method prepareForInstrumentation(Set<Class<?>>) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithMoreOverrides is overridden by the generated Bytecode DSL class." +
466+
" You can suppress this warning by re-declaring the method as abstract in this class."})
462467
@GenerateBytecode(languageClass = ErrorLanguage.class, enableTagInstrumentation = true)
463468
public abstract static class AcceptableOverrides extends RootNodeWithMoreOverrides implements BytecodeRootNode {
464469
protected AcceptableOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) {
@@ -473,19 +478,47 @@ static int add(int x, int y) {
473478
}
474479
}
475480

476-
@ExpectWarning("This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.")
481+
@ExpectWarning("This method is overridden by the generated Bytecode DSL class. It is unreachable and can be removed.")
477482
@Override
478483
public int findBytecodeIndex(Node node, Frame frame) {
479484
return super.findBytecodeIndex(node, frame);
480485
}
481486

482-
@ExpectWarning("This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.")
487+
@ExpectWarning("This method is overridden by the generated Bytecode DSL class. It is unreachable and can be removed. Bytecode DSL interpreters should use GenerateBytecode#captureFramesForTrace instead.")
483488
@Override
484489
public boolean isCaptureFramesForTrace(boolean compiledFrame) {
485490
return super.isCaptureFramesForTrace(compiledFrame);
486491
}
487492
}
488493

494+
// Unlike AcceptableOverrides, this class suppresses warnings about inherited overrides by
495+
// redeclaring them as abstract.
496+
@GenerateBytecode(languageClass = ErrorLanguage.class, enableTagInstrumentation = true)
497+
public abstract static class SuppressedOverrideWarnings extends RootNodeWithMoreOverrides implements BytecodeRootNode {
498+
499+
protected SuppressedOverrideWarnings(ErrorLanguage language, FrameDescriptor frameDescriptor) {
500+
super(language, frameDescriptor);
501+
}
502+
503+
@Operation
504+
public static final class Add {
505+
@Specialization
506+
static int add(int x, int y) {
507+
return x + y;
508+
}
509+
}
510+
511+
@Override
512+
protected abstract Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex);
513+
514+
@Override
515+
public abstract boolean isInstrumentable();
516+
517+
@Override
518+
public abstract void prepareForInstrumentation(Set<Class<?>> tags);
519+
520+
}
521+
489522
private abstract static class RootNodeWithOverrides extends RootNode {
490523
protected RootNodeWithOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) {
491524
super(language, frameDescriptor);
@@ -517,6 +550,63 @@ public void prepareForInstrumentation(Set<Class<?>> tags) {
517550
super.prepareForInstrumentation(tags);
518551
}
519552

553+
@Override
554+
protected boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
555+
return super.prepareForCompilation(rootCompilation, compilationTier, lastTier);
556+
}
557+
558+
}
559+
560+
@GenerateBytecode(languageClass = ErrorLanguage.class, enableTagInstrumentation = true)
561+
@ExpectError({
562+
"Method findInstrumentableCallNode(Node, Frame, int) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithFinalOverrides is overridden by the generated Bytecode DSL class." +
563+
" It cannot be declared final.",
564+
"Method prepareForInstrumentation(Set<Class<?>>) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithFinalOverrides is overridden by the generated Bytecode DSL class." +
565+
" It cannot be declared final.",
566+
"Method isCaptureFramesForTrace(boolean) in supertype com.oracle.truffle.api.bytecode.test.error_tests.ErrorTests.RootNodeWithFinalOverrides is overridden by the generated Bytecode DSL class." +
567+
" It cannot be declared final. Bytecode DSL interpreters should use GenerateBytecode#captureFramesForTrace instead."
568+
})
569+
public abstract static class InheritsFinalOverrides extends RootNodeWithFinalOverrides implements BytecodeRootNode {
570+
571+
protected InheritsFinalOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) {
572+
super(language, frameDescriptor);
573+
}
574+
575+
@Operation
576+
public static final class Add {
577+
@Specialization
578+
static int add(int x, int y) {
579+
return x + y;
580+
}
581+
}
582+
583+
}
584+
585+
private abstract static class RootNodeWithFinalOverrides extends RootNode {
586+
protected RootNodeWithFinalOverrides(ErrorLanguage language, FrameDescriptor frameDescriptor) {
587+
super(language, frameDescriptor);
588+
}
589+
590+
@Override
591+
protected final Node findInstrumentableCallNode(Node callNode, Frame frame, int bytecodeIndex) {
592+
return super.findInstrumentableCallNode(callNode, frame, bytecodeIndex);
593+
}
594+
595+
@Override
596+
protected final boolean isCaptureFramesForTrace(boolean compiledFrame) {
597+
return super.isCaptureFramesForTrace(compiledFrame);
598+
}
599+
600+
@Override
601+
public final void prepareForInstrumentation(Set<Class<?>> tags) {
602+
super.prepareForInstrumentation(tags);
603+
}
604+
605+
// This one is OK, we only override if not final
606+
@Override
607+
protected final Object translateStackTraceElement(TruffleStackTraceElement element) {
608+
return super.translateStackTraceElement(element);
609+
}
520610
}
521611

522612
@ExpectError("The used type system is invalid. Fix errors in the type system first.")

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,13 @@ private CodeExecutableElement createFindInstrumentableCallNode() {
904904
CodeTreeBuilder b = ex.createBuilder();
905905
b.startDeclaration(types.BytecodeNode, "bc").startStaticCall(types.BytecodeNode, "get").string("callNode").end().end();
906906
b.startIf().string("bc == null || !(bc instanceof AbstractBytecodeNode bytecodeNode)").end().startBlock();
907-
b.startReturn().string("super.findInstrumentableCallNode(callNode, frame, bytecodeIndex)").end();
907+
ExecutableElement superImpl = ElementUtils.findMethodInClassHierarchy(ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"), model.templateType);
908+
if (superImpl.getModifiers().contains(ABSTRACT)) {
909+
// edge case: root node could redeclare findInstrumentableCallNode as abstract.
910+
b.startReturn().string("null").end();
911+
} else {
912+
b.startReturn().string("super.findInstrumentableCallNode(callNode, frame, bytecodeIndex)").end();
913+
}
908914
b.end();
909915
b.statement("return bytecodeNode.findInstrumentableCallNode(bytecodeIndex)");
910916
return ex;
@@ -1705,8 +1711,8 @@ private CodeExecutableElement createPrepareForCompilation() {
17051711
// Disable compilation for the uncached interpreter.
17061712
b.string("bytecode.getTier() != ").staticReference(types.BytecodeTier, "UNCACHED");
17071713

1708-
ExecutableElement parentImpl = ElementUtils.findOverride(ElementUtils.findMethod(types.RootNode, "prepareForCompilation", 3), model.templateType);
1709-
if (parentImpl != null) {
1714+
ExecutableElement parentImpl = ElementUtils.findMethodInClassHierarchy(ElementUtils.findMethod(types.RootNode, "prepareForCompilation", 3), model.templateType);
1715+
if (parentImpl != null && !parentImpl.getModifiers().contains(ABSTRACT)) {
17101716
// Delegate to the parent impl.
17111717
b.string(" && ").startCall("super.prepareForCompilation").variables(ex.getParameters()).end();
17121718
}

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/parser/BytecodeDSLParser.java

Lines changed: 87 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -387,53 +387,7 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod
387387
model.interceptInternalException = ElementUtils.findMethod(typeElement, "interceptInternalException");
388388
model.interceptTruffleException = ElementUtils.findMethod(typeElement, "interceptTruffleException");
389389

390-
// Detect method implementations that will be overridden by the generated class.
391-
List<ExecutableElement> overrides = new ArrayList<>(List.of(
392-
ElementUtils.findMethod(types.RootNode, "execute"),
393-
ElementUtils.findMethod(types.RootNode, "computeSize"),
394-
ElementUtils.findMethod(types.RootNode, "findBytecodeIndex"),
395-
ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"),
396-
ElementUtils.findMethod(types.RootNode, "isInstrumentable"),
397-
ElementUtils.findMethod(types.RootNode, "isCaptureFramesForTrace"),
398-
ElementUtils.findMethod(types.RootNode, "prepareForCall"),
399-
ElementUtils.findMethod(types.RootNode, "prepareForInstrumentation"),
400-
ElementUtils.findMethod(types.BytecodeRootNode, "getBytecodeNode"),
401-
ElementUtils.findMethod(types.BytecodeRootNode, "getRootNodes"),
402-
ElementUtils.findMethod(types.BytecodeOSRNode, "executeOSR"),
403-
ElementUtils.findMethod(types.BytecodeOSRNode, "getOSRMetadata"),
404-
ElementUtils.findMethod(types.BytecodeOSRNode, "setOSRMetadata"),
405-
ElementUtils.findMethod(types.BytecodeOSRNode, "storeParentFrameInArguments"),
406-
ElementUtils.findMethod(types.BytecodeOSRNode, "restoreParentFrameFromArguments")));
407-
408-
for (ExecutableElement override : overrides) {
409-
ExecutableElement declared = ElementUtils.findMethod(typeElement, override.getSimpleName().toString());
410-
if (declared == null) {
411-
continue;
412-
}
413-
414-
if (declared.getModifiers().contains(Modifier.FINAL)) {
415-
model.addError(declared,
416-
"This method is overridden by the generated Bytecode DSL class, so it cannot be declared final. " +
417-
"You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it.");
418-
} else {
419-
model.addWarning(declared, "This method is overridden by the generated Bytecode DSL class, so this definition is unreachable and can be removed.");
420-
}
421-
}
422-
423-
List<ExecutableElement> overridesWithDelegation = new ArrayList<>(List.of(
424-
ElementUtils.findMethod(types.RootNode, "prepareForCompilation")));
425-
426-
for (ExecutableElement override : overridesWithDelegation) {
427-
ExecutableElement declared = ElementUtils.findMethod(typeElement, override.getSimpleName().toString());
428-
if (declared == null) {
429-
continue;
430-
}
431-
432-
if (declared.getModifiers().contains(Modifier.FINAL)) {
433-
model.addError(declared, "This method is overridden by the generated Bytecode DSL class, so it cannot be declared final.");
434-
}
435-
}
436-
390+
checkRootNodeOverrides(typeElement, model);
437391
if (model.hasErrors()) {
438392
return;
439393
}
@@ -710,6 +664,92 @@ private void parseBytecodeDSLModel(TypeElement typeElement, BytecodeDSLModel mod
710664
return;
711665
}
712666

667+
/**
668+
* Detect method implementations that will be overridden by the generated class and report an
669+
* appropriate message.
670+
*/
671+
private void checkRootNodeOverrides(TypeElement typeElement, BytecodeDSLModel model) {
672+
List<ExecutableElement> overrides = new ArrayList<>(List.of(
673+
ElementUtils.findMethod(types.RootNode, "execute"),
674+
ElementUtils.findMethod(types.RootNode, "computeSize"),
675+
ElementUtils.findMethod(types.RootNode, "findBytecodeIndex"),
676+
ElementUtils.findMethod(types.RootNode, "isInstrumentable"),
677+
ElementUtils.findMethod(types.RootNode, "prepareForCall"),
678+
ElementUtils.findMethod(types.RootNode, "prepareForInstrumentation"),
679+
ElementUtils.findMethod(types.BytecodeRootNode, "getBytecodeNode"),
680+
ElementUtils.findMethod(types.BytecodeRootNode, "getRootNodes"),
681+
ElementUtils.findMethod(types.BytecodeOSRNode, "executeOSR"),
682+
ElementUtils.findMethod(types.BytecodeOSRNode, "getOSRMetadata"),
683+
ElementUtils.findMethod(types.BytecodeOSRNode, "setOSRMetadata"),
684+
ElementUtils.findMethod(types.BytecodeOSRNode, "storeParentFrameInArguments"),
685+
ElementUtils.findMethod(types.BytecodeOSRNode, "restoreParentFrameFromArguments")));
686+
687+
for (ExecutableElement override : overrides) {
688+
checkRootNodeOverride(typeElement, override, model, false, null);
689+
}
690+
691+
String captureFramesForTraceMessage = String.format("Bytecode DSL interpreters should use %s#captureFramesForTrace instead.", getSimpleName(types.GenerateBytecode));
692+
checkRootNodeOverride(typeElement, ElementUtils.findInstanceMethod(context.getTypeElement(types.RootNode), "isCaptureFramesForTrace", new TypeMirror[]{context.getType(boolean.class)}), model,
693+
false, captureFramesForTraceMessage);
694+
695+
List<ExecutableElement> overridesWithDelegation = new ArrayList<>(List.of(
696+
ElementUtils.findMethod(types.RootNode, "findInstrumentableCallNode"),
697+
ElementUtils.findMethod(types.RootNode, "prepareForCompilation")));
698+
699+
for (ExecutableElement override : overridesWithDelegation) {
700+
checkRootNodeOverride(typeElement, override, model, true, null);
701+
}
702+
}
703+
704+
private void checkRootNodeOverride(TypeElement typeElement, ExecutableElement rootNodeMethod, BytecodeDSLModel model, boolean delegated, String customMessage) {
705+
ExecutableElement override = ElementUtils.findOverride(rootNodeMethod, typeElement);
706+
if (override == null || ElementUtils.typeEquals(override.getEnclosingElement().asType(), types.RootNode) || override.getModifiers().contains(Modifier.ABSTRACT)) {
707+
return;
708+
}
709+
boolean inherited = !override.getEnclosingElement().equals(typeElement);
710+
711+
Element messageElement;
712+
String message;
713+
if (inherited) {
714+
messageElement = typeElement;
715+
message = String.format("Method %s in supertype %s", ElementUtils.getReadableSignature(override), override.getEnclosingElement());
716+
} else {
717+
messageElement = override;
718+
message = "This method";
719+
}
720+
message += " is overridden by the generated Bytecode DSL class.";
721+
722+
if (override.getModifiers().contains(Modifier.FINAL)) {
723+
model.addError(messageElement, message + " " + badFinalOverrideMessage(delegated, inherited, customMessage));
724+
} else if (!delegated) {
725+
model.addWarning(messageElement, message + " " + badOverrideMessage(inherited, customMessage));
726+
}
727+
}
728+
729+
private static String badFinalOverrideMessage(boolean delegated, boolean inherited, String customMessage) {
730+
String message = "It cannot be declared final.";
731+
if (!delegated && !inherited) {
732+
message += " You can remove the final modifier to resolve this issue, but since the override will make this method unreachable, it is recommended to simply remove it.";
733+
}
734+
if (customMessage != null) {
735+
message += " " + customMessage;
736+
}
737+
return message;
738+
}
739+
740+
private static String badOverrideMessage(boolean inherited, String customMessage) {
741+
String message;
742+
if (inherited) {
743+
message = "You can suppress this warning by re-declaring the method as abstract in this class.";
744+
} else {
745+
message = "It is unreachable and can be removed.";
746+
}
747+
if (customMessage != null) {
748+
message += " " + customMessage;
749+
}
750+
return message;
751+
}
752+
713753
private void resolveBoxingElimination(BytecodeDSLModel model, List<QuickenDecision> manualQuickenings) {
714754
/*
715755
* If boxing elimination is enabled and the language uses operations with statically known

0 commit comments

Comments
 (0)