7878public class WasmValidator extends WasmVisitor {
7979 static class CtrlFrame {
8080 final WasmId .Label label ;
81+ final WasmValType returnType ;
82+ /**
83+ * Whether the end of this frame (block) is unreachable. There might still be reachable code
84+ * in the frame, but if this is true, execution can never reach the end of the block.
85+ */
8186 boolean unreachable = false ;
87+ /**
88+ * Whether any instructions target this block.
89+ */
90+ boolean targeted = false ;
8291
83- CtrlFrame (WasmId .Label label ) {
92+ CtrlFrame (WasmId .Label label , WasmValType returnType ) {
8493 this .label = label ;
94+ this .returnType = returnType ;
95+ }
96+
97+ public WasmValType getReturnType () {
98+ return returnType ;
8599 }
86100 }
87101
@@ -343,21 +357,30 @@ private void errorIf(boolean condition, String msg) {
343357 }
344358 }
345359
346- private static boolean assertIdsEqual (WasmId first , WasmId second ) {
360+ private static boolean idsEqual (WasmId first , WasmId second ) {
347361 return Objects .equals (first , second );
348362 }
349363
350364 private CtrlFrame topFrame () {
351365 return Objects .requireNonNull (ctrls .peek ());
352366 }
353367
368+ /**
369+ * Whether the current frame is marked as unreachable.
370+ *
371+ * @see #markUnreachable()
372+ */
373+ private boolean isUnreachable () {
374+ return topFrame ().unreachable ;
375+ }
376+
354377 private void pushVal (WasmValType t ) {
378+ errorIf (isUnreachable (), "Tried to push " + t + " when unreachable" );
355379 vals .push (t );
356380 }
357381
358382 private WasmValType popVal () {
359- CtrlFrame top = topFrame ();
360- if (vals .isEmpty () && top .unreachable ) {
383+ if (isUnreachable ()) {
361384 return null ;
362385 }
363386
@@ -383,6 +406,10 @@ private RuntimeException typeMismatch(WasmValType[] expected, Deque<WasmValType>
383406 * @param expected The top of the stack must match this (the last element is at the very top).
384407 */
385408 private void popVals (WasmValType ... expected ) {
409+ if (isUnreachable ()) {
410+ return ;
411+ }
412+
386413 int numTypes = expected .length ;
387414 if (vals .size () < numTypes ) {
388415 throw typeMismatch (expected );
@@ -416,12 +443,20 @@ private Global getAndAssertGlobalExists(WasmId.Global global) {
416443 }
417444
418445 private void pushCtrl (WasmId .Label label ) {
446+ pushCtrl (label , null );
447+ }
448+
449+ private void pushCtrl (WasmId .Label label , WasmValType resultType ) {
419450 assertStackEmpty ();
420451 if (label != null ) {
421452 // No name conflicts.
422453 assertIdUniqueName (label , ctrls .stream ().map (frame -> frame .label ).filter (Objects ::nonNull ).collect (Collectors .toList ()));
423454 }
424- ctrls .push (new CtrlFrame (label ));
455+ ctrls .push (new CtrlFrame (label , resultType ));
456+ }
457+
458+ private void pushBlockCtrl (Instruction .WasmBlock block ) {
459+ pushCtrl (block .getLabel (), block .getResult ());
425460 }
426461
427462 private void popCtrl (WasmId .Label expectedLabel ) {
@@ -430,6 +465,15 @@ private void popCtrl(WasmId.Label expectedLabel) {
430465 assertStackEmpty ();
431466 errorIf (frame .label != expectedLabel , "Expected control frame " + expectedLabel + " but got " + frame .label );
432467 ctrls .pop ();
468+ /*
469+ * If the end of the frame was unreachable and no instruction targeted it, it means that it
470+ * is impossible to reach the instruction right after it and we have to mark the outer block
471+ * as unreachable as well.
472+ */
473+ if (!frame .targeted && frame .unreachable && !ctrls .isEmpty ()) {
474+ markUnreachable ();
475+ }
476+
433477 }
434478
435479 private void popBlockCtrl (Instruction .WasmBlock block ) {
@@ -442,7 +486,16 @@ private void popBlockCtrl(Instruction.WasmBlock block) {
442486 }
443487 }
444488
445- private void unreachable () {
489+ /**
490+ * Marks the top frame as unreachable.
491+ * <p>
492+ * An unreachable frame satisfies all requirements on the operand stack (i.e.
493+ * {@link #popVals(WasmValType...) never errors.}).
494+ * <p>
495+ * Call this method after visiting any instruction at which execution stops and never returns
496+ * (e.g. {@code br}).
497+ */
498+ private void markUnreachable () {
446499 CtrlFrame top = topFrame ();
447500 vals .clear ();
448501 top .unreachable = true ;
@@ -461,8 +514,15 @@ private void applyTypeUse(TypeUse typeUse) {
461514 typeUse .results .forEach (this ::pushVal );
462515 }
463516
464- private void assertLabelExists (WasmId .Label label ) {
465- errorIf (ctrls .stream ().noneMatch (frame -> assertIdsEqual (label , frame .label )), "Label " + label + " does not exist." );
517+ private CtrlFrame markLabelTargeted (WasmId .Label label ) {
518+ CtrlFrame frame = ctrls .stream ().filter (f -> idsEqual (label , f .label )).findFirst ().orElseThrow (() -> error ("Label " + label + " does not exist." ));
519+ frame .targeted = true ;
520+ return frame ;
521+ }
522+
523+ private void markLabelTargetedWithReturnType (WasmId .Label label , WasmValType returnType ) {
524+ CtrlFrame frame = markLabelTargeted (label );
525+ errorIf (!Objects .equals (frame .getReturnType (), returnType ), "Label " + label + " has return type " + frame .getReturnType () + " but " + returnType + " was expected" );
466526 }
467527
468528 /**
@@ -576,14 +636,14 @@ public void visitInstruction(Instruction inst) {
576636
577637 @ Override
578638 public void visitBlock (Instruction .Block block ) {
579- pushCtrl (block . getLabel () );
639+ pushBlockCtrl (block );
580640 super .visitBlock (block );
581641 popBlockCtrl (block );
582642 }
583643
584644 @ Override
585645 public void visitLoop (Instruction .Loop loop ) {
586- pushCtrl (loop . getLabel () );
646+ pushBlockCtrl (loop );
587647 super .visitLoop (loop );
588648 popBlockCtrl (loop );
589649 }
@@ -592,19 +652,63 @@ public void visitLoop(Instruction.Loop loop) {
592652 public void visitIf (Instruction .If ifBlock ) {
593653 visitInstruction (ifBlock .condition );
594654 popVals (i32 );
595- pushCtrl (ifBlock .getLabel ());
655+ pushBlockCtrl (ifBlock );
656+
657+ /*
658+ * Propagating the information about unreachability upward here requires some more work. We
659+ * only want to mark the end of the if-block as unreachable if both the end of the then- and
660+ * else-branches are unreachable.
661+ */
662+ boolean thenBlockUnreachable = false ;
663+ boolean elseBlockUnreachable = false ;
664+
665+ // Control frame around both branches to intercept the unreachable state
666+ pushCtrl (null );
667+
668+ // Control frame around the then-branch
669+ pushCtrl (null );
596670 visitInstructions (ifBlock .thenInstructions );
597- popBlockCtrl (ifBlock );
671+ if (topFrame ().unreachable ) {
672+ thenBlockUnreachable = true ;
673+ topFrame ().unreachable = false ;
674+ }
675+ popCtrl (null );
676+
598677 if (ifBlock .hasElse ()) {
599- pushCtrl (ifBlock . getLabel () );
678+ pushCtrl (null );
600679 visitInstructions (ifBlock .elseInstructions );
601- popBlockCtrl (ifBlock );
680+ if (topFrame ().unreachable ) {
681+ elseBlockUnreachable = true ;
682+ topFrame ().unreachable = false ;
683+ }
684+ popCtrl (null );
685+ }
686+ if (thenBlockUnreachable && elseBlockUnreachable ) {
687+ // This will mark the parent block as unreachable once we pop this control frame.
688+ markUnreachable ();
602689 }
690+ popCtrl (null );
691+ popBlockCtrl (ifBlock );
692+ }
693+
694+ @ Override
695+ public void visitTryTable (Instruction .TryTable tryBlock ) {
696+ tryBlock .catchBlocks .forEach (this ::assertCatchValid );
697+ pushBlockCtrl (tryBlock );
698+ visitInstructions (tryBlock .instructions );
699+ popBlockCtrl (tryBlock );
700+ }
701+
702+ private void assertCatchValid (Instruction .TryTable .Catch catchClause ) {
703+ errorIf (!ctxt .hasTag (catchClause .tag ), "No matching tag for catch clause: " + catchClause );
704+ List <WasmValType > catchParams = catchClause .tag .typeUse .params ;
705+ errorIf (catchParams .size () != 1 , "Can only support catch clause tags with a single param, got" + catchParams .size ());
706+ markLabelTargetedWithReturnType (catchClause .label , catchParams .getFirst ());
603707 }
604708
605709 @ Override
606710 public void visitTry (Instruction .Try tryBlock ) {
607- pushCtrl (tryBlock . getLabel () );
711+ pushBlockCtrl (tryBlock );
608712 visitInstructions (tryBlock .instructions );
609713
610714 for (Instruction .Try .Catch catchBlock : tryBlock .catchBlocks ) {
@@ -620,7 +724,7 @@ public void visitTry(Instruction.Try tryBlock) {
620724
621725 @ Override
622726 public void visitUnreachable (Instruction .Unreachable unreachable ) {
623- unreachable ();
727+ markUnreachable ();
624728 super .visitUnreachable (unreachable );
625729 }
626730
@@ -642,10 +746,10 @@ public void visitBreak(Instruction.Break inst) {
642746
643747 WasmId .Label targetLabel = inst .getTarget ();
644748
645- assertLabelExists (targetLabel );
749+ markLabelTargeted (targetLabel );
646750
647751 if (inst .condition == null ) {
648- unreachable ();
752+ markUnreachable ();
649753 } else {
650754 popVals (i32 );
651755 }
@@ -658,13 +762,13 @@ public void visitBreakTable(Instruction.BreakTable inst) {
658762 popVals (i32 );
659763
660764 WasmId .Label defaultLabel = inst .getDefaultTarget ();
661- assertLabelExists (defaultLabel );
765+ markLabelTargeted (defaultLabel );
662766
663767 for (int i = 0 ; i < inst .numTargets (); i ++) {
664- assertLabelExists (inst .getTarget (i ));
768+ markLabelTargeted (inst .getTarget (i ));
665769 }
666770
667- unreachable ();
771+ markUnreachable ();
668772 }
669773
670774 @ Override
@@ -753,6 +857,7 @@ public void visitThrow(Instruction.Throw inst) {
753857
754858 errorIf (!ctxt .hasTag (inst .tag ), "No matching tag for throw: " + inst );
755859 applyTypeUse (inst .tag .typeUse );
860+ markUnreachable ();
756861 }
757862
758863 @ Override
0 commit comments