|
14 | 14 | class TagPropagator extends MethodVisitor { |
15 | 15 | private final ShadowLocals shadowLocals; |
16 | 16 |
|
17 | | - private TagPropagator(ShadowLocals shadowLocals, MethodVisitor mv) { |
| 17 | + /** |
| 18 | + * True when the class being instrumented is an application class whose classloader can |
| 19 | + * resolve PathUtils (knarr-runtime) at runtime. False for JDK bootstrap classes (java/, |
| 20 | + * sun/, jdk/, javax/, com/sun/) which use the bootstrap classloader and cannot access |
| 21 | + * PathUtils. When false, branch constraint injection is skipped even if |
| 22 | + * galette.symbolic.enabled=true. |
| 23 | + */ |
| 24 | + private final boolean isAppClass; |
| 25 | + |
| 26 | + private TagPropagator(ShadowLocals shadowLocals, MethodVisitor mv, boolean isAppClass) { |
18 | 27 | super(GaletteTransformer.ASM_VERSION, mv); |
19 | 28 | if (shadowLocals == null) { |
20 | 29 | throw new NullPointerException(); |
21 | 30 | } |
22 | 31 | this.shadowLocals = shadowLocals; |
| 32 | + this.isAppClass = isAppClass; |
23 | 33 | } |
24 | 34 |
|
25 | 35 | @Override |
@@ -571,25 +581,34 @@ public void visitJumpInsn(int opcode, Label label) { |
571 | 581 | case IFGE: |
572 | 582 | case IFGT: |
573 | 583 | case IFLE: |
574 | | - case IFNULL: |
575 | | - case IFNONNULL: |
576 | | - // ..., value -> ... |
577 | | - // Before popping, record constraint if symbolic execution is enabled |
578 | | - if (isSymbolicExecutionEnabled()) { |
| 584 | + // ..., int-value -> ... |
| 585 | + if (shouldRecordBranchConstraints()) { |
579 | 586 | recordBranchConstraint(opcode, label); |
580 | | - // recordBranchConstraint handles the jump instruction itself |
581 | | - // So we don't call super.visitJumpInsn for these opcodes |
| 587 | + // recordBranchConstraint emits the original jump instruction |
582 | 588 | shadowLocals.pop(1); |
583 | | - return; // Early return - don't execute super.visitJumpInsn |
| 589 | + return; |
584 | 590 | } |
585 | 591 | shadowLocals.pop(1); |
586 | 592 | break; |
| 593 | + case IFNULL: |
| 594 | + case IFNONNULL: |
| 595 | + // ..., objectref -> ... (reference comparison; no integer constraint recording) |
| 596 | + shadowLocals.pop(1); |
| 597 | + break; |
587 | 598 | case IF_ICMPEQ: |
588 | 599 | case IF_ICMPNE: |
589 | 600 | case IF_ICMPLT: |
590 | 601 | case IF_ICMPGE: |
591 | 602 | case IF_ICMPGT: |
592 | 603 | case IF_ICMPLE: |
| 604 | + // ..., value1, value2 -> ... |
| 605 | + if (shouldRecordBranchConstraints()) { |
| 606 | + recordTwoValueBranchConstraint(opcode, label); |
| 607 | + shadowLocals.pop(2); |
| 608 | + return; |
| 609 | + } |
| 610 | + shadowLocals.pop(2); |
| 611 | + break; |
593 | 612 | case IF_ACMPEQ: |
594 | 613 | case IF_ACMPNE: |
595 | 614 | // ..., value1, value2 -> ... |
@@ -753,124 +772,124 @@ private static boolean isMirroredField(String owner, String name, boolean isStat |
753 | 772 | private static final String TAG_DESC = "Ledu/neu/ccs/prl/galette/internal/runtime/Tag;"; |
754 | 773 |
|
755 | 774 | /** |
756 | | - * Check if symbolic execution is enabled. |
| 775 | + * Check if branch constraint recording should be performed for this method. |
| 776 | + * Returns true only when symbolic execution is globally enabled AND the class |
| 777 | + * being instrumented is an application class (not a JDK bootstrap class). |
757 | 778 | */ |
758 | | - private static boolean isSymbolicExecutionEnabled() { |
759 | | - return SYMBOLIC_EXECUTION_ENABLED; |
| 779 | + private boolean shouldRecordBranchConstraints() { |
| 780 | + return SYMBOLIC_EXECUTION_ENABLED && isAppClass; |
760 | 781 | } |
761 | 782 |
|
762 | 783 | /** |
763 | | - * Record a branch constraint by peeking at the tag on the shadow stack. |
764 | | - * Called before the tag is popped for branch instructions (IFEQ, IFNE, etc.). |
| 784 | + * Record a branch constraint for single-value branch instructions (IFEQ, IFLT, IFGE etc.). |
| 785 | + * Uses value-based tag lookup via PathUtils.testAndRecordSingleValueBranch. |
| 786 | + * |
| 787 | + * No new branch-target labels are introduced — the constraint method is called BEFORE |
| 788 | + * the original jump instruction, so no stack-map frames need to be emitted. |
765 | 789 | * |
766 | | - * Pattern (based on Phosphor's PathConstraintTagFactory): |
767 | | - * 1. Peek at tag on shadow stack |
768 | | - * 2. Check if tag is null (concrete value) |
769 | | - * 3. If null, execute normal branch |
770 | | - * 4. If not null (symbolic), create two paths: |
771 | | - * a. Path where branch is taken → record constraint with branchTaken=true |
772 | | - * b. Path where branch is NOT taken → record constraint with branchTaken=false |
| 790 | + * Stack contract: |
| 791 | + * Entry: ..., value |
| 792 | + * Exit: jumps to {@code label} if branch taken, falls through otherwise |
| 793 | + * (identical to the original branch instruction) |
773 | 794 | */ |
774 | 795 | private void recordBranchConstraint(int opcode, Label label) { |
775 | | - // Create fresh labels for the instrumentation |
776 | | - Label willJump = new Label(); // Branch will be taken (symbolic) |
777 | | - Label untainted = new Label(); // No symbolic tag (concrete value) |
778 | | - Label originalEnd = new Label(); // Merge point after constraint recording |
779 | | - |
780 | | - // Stack at this point: ..., value |
781 | | - // Shadow stack: ..., tag |
782 | | - |
783 | | - // Duplicate the value for testing the branch condition twice |
784 | | - super.visitInsn(DUP); |
785 | | - // Stack: ..., value, value |
786 | | - |
787 | | - // Peek at the tag from shadow stack to check if it's symbolic |
788 | | - shadowLocals.peek(0); |
789 | | - // Stack: ..., value, value, tag |
790 | | - |
791 | | - // Check if tag is null (concrete value, no symbolic execution needed) |
| 796 | + // Stack: ..., value |
792 | 797 | super.visitInsn(DUP); |
793 | | - super.visitJumpInsn(IFNULL, untainted); |
794 | | - // Stack: ..., value, value, tag |
795 | | - |
796 | | - // Tag is NOT null (symbolic value) - we need to record constraints for both paths |
797 | | - |
798 | | - // Test the branch condition to see which path we're taking |
799 | | - super.visitInsn(POP); // Remove the tag from stack |
800 | 798 | // Stack: ..., value, value |
801 | | - |
802 | | - super.visitJumpInsn(opcode, willJump); |
803 | | - // Stack: ..., value |
804 | | - |
805 | | - // === Path 1: Branch NOT taken === |
806 | | - // Peek tag again for recording |
807 | | - shadowLocals.peek(0); |
808 | | - // Stack: ..., value, tag |
809 | | - |
810 | | - // Load opcode |
811 | 799 | AsmUtil.pushInt(mv, opcode); |
812 | | - // Stack: ..., value, tag, opcode |
813 | | - |
814 | | - // Load branchTaken = false |
815 | | - super.visitInsn(ICONST_0); |
816 | | - // Stack: ..., value, tag, opcode, false |
817 | | - |
818 | | - // Call PathUtils.recordBranchConstraint(tag, opcode, branchTaken) |
819 | | - super.visitMethodInsn( |
820 | | - INVOKESTATIC, PATH_UTILS_INTERNAL_NAME, "recordBranchConstraint", "(" + TAG_DESC + "IZ)V", false); |
821 | | - // Stack: ..., value |
822 | | - |
823 | | - super.visitJumpInsn(GOTO, originalEnd); |
824 | | - |
825 | | - // === Path 2: Branch WILL be taken === |
826 | | - super.visitLabel(willJump); |
827 | | - // Stack: ..., value |
828 | | - |
829 | | - // Peek tag again for recording |
830 | | - shadowLocals.peek(0); |
831 | | - // Stack: ..., value, tag |
832 | | - |
833 | | - // Load opcode |
834 | | - AsmUtil.pushInt(mv, opcode); |
835 | | - // Stack: ..., value, tag, opcode |
836 | | - |
837 | | - // Load branchTaken = true |
838 | | - super.visitInsn(ICONST_1); |
839 | | - // Stack: ..., value, tag, opcode, true |
840 | | - |
841 | | - // Call PathUtils.recordBranchConstraint(tag, opcode, branchTaken) |
842 | | - super.visitMethodInsn( |
843 | | - INVOKESTATIC, PATH_UTILS_INTERNAL_NAME, "recordBranchConstraint", "(" + TAG_DESC + "IZ)V", false); |
844 | | - // Stack: ..., value |
845 | | - |
846 | | - // Now execute the original branch (jump to the user's label) |
| 800 | + // Stack: ..., value, value, opcode |
| 801 | + // testAndRecordSingleValueBranch(int value, int opcode): tests condition internally and records constraint |
| 802 | + super.visitMethodInsn(INVOKESTATIC, PATH_UTILS_INTERNAL_NAME, "testAndRecordSingleValueBranch", "(II)V", false); |
| 803 | + // Stack: ..., value (copy and opcode consumed by method) |
| 804 | + // Emit the original branch — no new labels needed, no VerifyError. |
847 | 805 | super.visitJumpInsn(opcode, label); |
| 806 | + // Stack: ... (value consumed by branch) |
| 807 | + } |
848 | 808 |
|
849 | | - // === Path 3: Untainted (concrete value, no symbolic tag) === |
850 | | - super.visitLabel(untainted); |
851 | | - // Stack: ..., value, value, tag (which is null) |
852 | | - |
853 | | - // Clean up: pop the null tag and duplicate value |
854 | | - super.visitInsn(POP); // Remove null tag |
855 | | - // Stack: ..., value, value |
856 | | - |
857 | | - // Execute normal branch |
| 809 | + /** |
| 810 | + * Record a branch constraint for two-value branch instructions (IF_ICMPEQ/LT/GE etc.). |
| 811 | + * Uses value-based tag lookup for the left operand (v1) via PathUtils.testAndRecordTwoValueBranch. |
| 812 | + * |
| 813 | + * No new branch-target labels are introduced — the constraint method is called BEFORE |
| 814 | + * the original jump instruction, so no stack-map frames need to be emitted. |
| 815 | + * |
| 816 | + * Stack contract: |
| 817 | + * Entry: ..., v1, v2 |
| 818 | + * Exit: jumps to {@code label} if branch taken, falls through otherwise |
| 819 | + * (identical to the original branch instruction) |
| 820 | + */ |
| 821 | + private void recordTwoValueBranchConstraint(int opcode, Label label) { |
| 822 | + // Stack: ..., v1, v2 |
| 823 | + super.visitInsn(DUP2); |
| 824 | + // Stack: ..., v1, v2, v1, v2 |
| 825 | + AsmUtil.pushInt(mv, opcode); |
| 826 | + // Stack: ..., v1, v2, v1, v2, opcode |
| 827 | + // testAndRecordTwoValueBranch(int v1, int v2, int opcode): tests + records constraint |
| 828 | + super.visitMethodInsn(INVOKESTATIC, PATH_UTILS_INTERNAL_NAME, "testAndRecordTwoValueBranch", "(III)V", false); |
| 829 | + // Stack: ..., v1, v2 (copies and opcode consumed by method) |
| 830 | + // Emit the original branch — no new labels needed, no VerifyError. |
858 | 831 | super.visitJumpInsn(opcode, label); |
859 | | - |
860 | | - // === Merge point === |
861 | | - super.visitLabel(originalEnd); |
862 | | - // Stack: ..., value |
863 | | - |
864 | | - // Note: The value will be popped by the normal visitJumpInsn flow |
| 832 | + // Stack: ... (v1, v2 consumed by branch) |
865 | 833 | } |
866 | 834 |
|
867 | 835 | // Note: Automatic switch constraint collection methods removed. |
868 | 836 | // Switch constraints are now collected manually via PathUtils API. |
869 | 837 | // See AutomaticVitruvPathExploration.executeVitruvWithInput() for implementation. |
870 | 838 |
|
| 839 | + /** |
| 840 | + * Comma-separated list of internal class-name prefixes that should have branch constraints |
| 841 | + * injected. Only classes whose internal name starts with one of these prefixes will have |
| 842 | + * {@code testAndRecordSingleValueBranch} / {@code testAndRecordTwoValueBranch} calls emitted. |
| 843 | + * |
| 844 | + * <p>Set via {@code -Dgalette.instrument.prefix=mir/routines/,tools/example/} on the JVM |
| 845 | + * command line. An empty (or absent) property means "instrument no class", which prevents |
| 846 | + * infinite recursion and spurious constraints from framework code. |
| 847 | + * |
| 848 | + * <p>JDK bootstrap classes and CoCoPath project classes are always excluded regardless. |
| 849 | + */ |
| 850 | + private static final String[] INSTRUMENT_PREFIXES = loadInstrumentPrefixes(); |
| 851 | + |
| 852 | + private static String[] loadInstrumentPrefixes() { |
| 853 | + String prop = System.getProperty("galette.instrument.prefix", ""); |
| 854 | + if (prop.isEmpty()) { |
| 855 | + return new String[0]; |
| 856 | + } |
| 857 | + return prop.split(","); |
| 858 | + } |
| 859 | + |
| 860 | + /** |
| 861 | + * Returns true when branch constraint injection should be applied to the given class. |
| 862 | + * The class must: |
| 863 | + * 1. Not be a JDK bootstrap class (java/, sun/, jdk/, javax/, com/sun/, com/oracle/) |
| 864 | + * 2. Not be a CoCoPath project class (edu/neu/ccs/prl/galette/) — would cause recursion |
| 865 | + * 3. Match at least one prefix in {@code galette.instrument.prefix} |
| 866 | + */ |
| 867 | + private static boolean isAppClass(String owner) { |
| 868 | + // Always exclude JDK bootstrap classes (can't resolve PathUtils from boot classloader) |
| 869 | + if (owner.startsWith("java/") |
| 870 | + || owner.startsWith("sun/") |
| 871 | + || owner.startsWith("jdk/") |
| 872 | + || owner.startsWith("javax/") |
| 873 | + || owner.startsWith("com/sun/") |
| 874 | + || owner.startsWith("com/oracle/")) { |
| 875 | + return false; |
| 876 | + } |
| 877 | + // Always exclude CoCoPath internals to prevent infinite recursion |
| 878 | + if (owner.startsWith("edu/neu/ccs/prl/galette/")) { |
| 879 | + return false; |
| 880 | + } |
| 881 | + // Require at least one prefix match |
| 882 | + for (String prefix : INSTRUMENT_PREFIXES) { |
| 883 | + if (!prefix.isEmpty() && owner.startsWith(prefix)) { |
| 884 | + return true; |
| 885 | + } |
| 886 | + } |
| 887 | + return false; |
| 888 | + } |
| 889 | + |
871 | 890 | static MethodVisitor newInstance(MethodVisitor mv, MethodNode original, boolean isShadow, String owner) { |
872 | 891 | ShadowLocals shadowLocals = ShadowLocals.newInstance(mv, original, isShadow); |
873 | | - TagPropagator propagator = new TagPropagator(shadowLocals, shadowLocals); |
| 892 | + TagPropagator propagator = new TagPropagator(shadowLocals, shadowLocals, isAppClass(owner)); |
874 | 893 | AnalyzerAdapter analyzer = |
875 | 894 | new AnalyzerAdapter(owner, original.access, original.name, original.desc, propagator); |
876 | 895 | IndirectFramePasser iPasser = new IndirectFramePasser(shadowLocals, analyzer, analyzer); |
|
0 commit comments