Skip to content

Commit f0dd570

Browse files
committed
Merge bowen into main
2 parents fd680ad + d625830 commit f0dd570

90 files changed

Lines changed: 12384 additions & 3335 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 58 additions & 423 deletions
Large diffs are not rendered by default.

COCOPATH_TECHNICAL_REPORT.md

Lines changed: 0 additions & 1234 deletions
This file was deleted.

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# CoCoPath - Concolic Exploration of Consistency-Preserving Paths
22

3+
---
4+
5+
> **ECMFA 2025 Second Round — Evaluation Data**
6+
>
7+
> ### Execution Time — BrakeSystem-ControlSystem Scenario (2 symbolic variables, 10 paths)
8+
>
9+
> | Path | Profile | Calibration | Constraints | Time (ms) |
10+
> |-----:|----------|--------------|------------:|----------:|
11+
> | P1 | Skip || 2 | 140 |
12+
> | P2 | Off-road | Conservative | 4 | 295 |
13+
> | P3 | Off-road | Standard | 4 | 101 |
14+
> | P4 | Off-road | Track | 3 | 109 |
15+
> | P5 | Comfort | Conservative | 3 | 192 |
16+
> | P6 | Comfort | Standard | 4 | 120 |
17+
> | P7 | Comfort | Track | 3 | 89 |
18+
> | P8 | Sport | Conservative | 2 | 161 |
19+
> | P9 | Sport | Standard | 3 | 107 |
20+
> | P10 | Sport | Track | 2 | 90 |
21+
>
22+
> | Paths | Initialisation (ms) | Avg. / Path (ms) | Total (ms) |
23+
> |------:|--------------------:|-----------------:|-----------:|
24+
> | 10 | 33,905 | 140.4 | 35,309 |
25+
---
26+
327
## Overview
428

529
CoCoPath is a concolic execution framework for systematically exploring execution paths in consistency-preserving model transformations. By combining dynamic taint tracking, concolic execution, and model transformation frameworks, CoCoPath enables automatic path exploration to derive potential target models based on source models, consistency preservation rules (CPRs), and optional domain constraints.

WORKFLOW_DIAGRAM.md

Lines changed: 0 additions & 568 deletions
This file was deleted.

amalthea-acset-integration/vsum/src/main/java/tools/vitruv/methodologisttemplate/vsum/Test.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class Test {
3333
* Single-variable version: Insert one task with one user choice.
3434
* Used for single-variable path exploration (5 paths).
3535
*/
36-
public void insertTask(Path projectDir, Integer userInput) {
36+
public void insertTask(Path projectDir, int userInput) {
3737
// 1)
3838
// Execute switch with tagged value to collect path constraints
3939
switch (userInput) {
File renamed without changes.

galette-agent/src/main/java/edu/neu/ccs/prl/galette/internal/transform/TagPropagator.java

Lines changed: 125 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@
1414
class TagPropagator extends MethodVisitor {
1515
private final ShadowLocals shadowLocals;
1616

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) {
1827
super(GaletteTransformer.ASM_VERSION, mv);
1928
if (shadowLocals == null) {
2029
throw new NullPointerException();
2130
}
2231
this.shadowLocals = shadowLocals;
32+
this.isAppClass = isAppClass;
2333
}
2434

2535
@Override
@@ -571,25 +581,34 @@ public void visitJumpInsn(int opcode, Label label) {
571581
case IFGE:
572582
case IFGT:
573583
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()) {
579586
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
582588
shadowLocals.pop(1);
583-
return; // Early return - don't execute super.visitJumpInsn
589+
return;
584590
}
585591
shadowLocals.pop(1);
586592
break;
593+
case IFNULL:
594+
case IFNONNULL:
595+
// ..., objectref -> ... (reference comparison; no integer constraint recording)
596+
shadowLocals.pop(1);
597+
break;
587598
case IF_ICMPEQ:
588599
case IF_ICMPNE:
589600
case IF_ICMPLT:
590601
case IF_ICMPGE:
591602
case IF_ICMPGT:
592603
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;
593612
case IF_ACMPEQ:
594613
case IF_ACMPNE:
595614
// ..., value1, value2 -> ...
@@ -753,124 +772,124 @@ private static boolean isMirroredField(String owner, String name, boolean isStat
753772
private static final String TAG_DESC = "Ledu/neu/ccs/prl/galette/internal/runtime/Tag;";
754773

755774
/**
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).
757778
*/
758-
private static boolean isSymbolicExecutionEnabled() {
759-
return SYMBOLIC_EXECUTION_ENABLED;
779+
private boolean shouldRecordBranchConstraints() {
780+
return SYMBOLIC_EXECUTION_ENABLED && isAppClass;
760781
}
761782

762783
/**
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.
765789
*
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)
773794
*/
774795
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
792797
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
800798
// 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
811799
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.
847805
super.visitJumpInsn(opcode, label);
806+
// Stack: ... (value consumed by branch)
807+
}
848808

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.
858831
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)
865833
}
866834

867835
// Note: Automatic switch constraint collection methods removed.
868836
// Switch constraints are now collected manually via PathUtils API.
869837
// See AutomaticVitruvPathExploration.executeVitruvWithInput() for implementation.
870838

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+
871890
static MethodVisitor newInstance(MethodVisitor mv, MethodNode original, boolean isShadow, String owner) {
872891
ShadowLocals shadowLocals = ShadowLocals.newInstance(mv, original, isShadow);
873-
TagPropagator propagator = new TagPropagator(shadowLocals, shadowLocals);
892+
TagPropagator propagator = new TagPropagator(shadowLocals, shadowLocals, isAppClass(owner));
874893
AnalyzerAdapter analyzer =
875894
new AnalyzerAdapter(owner, original.access, original.name, original.desc, propagator);
876895
IndirectFramePasser iPasser = new IndirectFramePasser(shadowLocals, analyzer, analyzer);

0 commit comments

Comments
 (0)