From bd4b473210c7d9c81ee2de8ea4fe5a79acdb7ac5 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Sun, 7 Sep 2025 18:03:54 -0400 Subject: [PATCH 01/13] 1) invent new sequence abstract type to be used in matches over list, tuple 2) handle first, most trivial case of MatchAs --- .../data/match1.py | 41 ++++++++++-------- .../wala/cast/python/loader/PythonLoader.java | 6 ++- .../wala/cast/python/types/PythonTypes.java | 3 ++ .../jep/ast/CPythonAstToCAstTranslator.java | 42 +++++++++++++++++-- .../cast/python/cpython/test/TestMatch.java | 13 +++++- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/core/com.ibm.wala.cast.python.test/data/match1.py b/core/com.ibm.wala.cast.python.test/data/match1.py index 816d12130..22e652047 100644 --- a/core/com.ibm.wala.cast.python.test/data/match1.py +++ b/core/com.ibm.wala.cast.python.test/data/match1.py @@ -25,21 +25,26 @@ def saturday(): def sunday(): print("Sunday") - -day = 4 - -match day: - case 1: - monday() - case 2: - tuesday() - case 3: - wednesday() - case 4: - thursday() - case 5: - friday() - case 6: - saturday() - case 7: - sunday() + +def otherDay(): + print("other day??") + + +for day in [1, 2, 3, 4, 5, 6, 7, otherDay]: + match day: + case 1: + monday() + case 2: + tuesday() + case 3: + wednesday() + case 4: + thursday() + case 5: + friday() + case 6: + saturday() + case 7: + sunday() + case x: + x() diff --git a/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/loader/PythonLoader.java b/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/loader/PythonLoader.java index d48c122c6..042d8cb7b 100644 --- a/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/loader/PythonLoader.java +++ b/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/loader/PythonLoader.java @@ -228,14 +228,16 @@ protected TranslatorToIR initTranslator(Set> topLe PythonTypes.comprehension.getName(), PythonTypes.CodeBody.getName(), this, null); final CoreClass object = new CoreClass(PythonTypes.object.getName(), PythonTypes.rootTypeName, this, null); + final CoreClass sequence = + new CoreClass(PythonTypes.sequence.getName(), PythonTypes.Root.getName(), this, null); final CoreClass list = - new CoreClass(PythonTypes.list.getName(), PythonTypes.Root.getName(), this, null); + new CoreClass(PythonTypes.list.getName(), PythonTypes.sequence.getName(), this, null); final CoreClass set = new CoreClass(PythonTypes.set.getName(), PythonTypes.Root.getName(), this, null); final CoreClass dict = new CoreClass(PythonTypes.dict.getName(), PythonTypes.Root.getName(), this, null); final CoreClass tuple = - new CoreClass(PythonTypes.tuple.getName(), PythonTypes.Root.getName(), this, null); + new CoreClass(PythonTypes.tuple.getName(), PythonTypes.sequence.getName(), this, null); final CoreClass string = new CoreClass(PythonTypes.string.getName(), PythonTypes.Root.getName(), this, null); final CoreClass trampoline = diff --git a/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/types/PythonTypes.java b/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/types/PythonTypes.java index 2baf404f0..076221881 100644 --- a/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/types/PythonTypes.java +++ b/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/types/PythonTypes.java @@ -103,6 +103,9 @@ public class PythonTypes extends AstTypeReference { public static final TypeReference coroutine = TypeReference.findOrCreate(pythonLoader, TypeName.findOrCreate("Lcoroutine")); + public static final TypeReference sequence = + TypeReference.findOrCreate(pythonLoader, TypeName.findOrCreate("Lsequence")); + /** https://docs.python.org/3/library/stdtypes.html#typeiter. */ public static final TypeReference iterator = TypeReference.findOrCreate(pythonLoader, TypeName.findOrCreate("Literator")); diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 0cee0feeb..9b5a9b943 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -404,6 +404,10 @@ default List inits() { default CAstNode matchVar() { return getParent().matchVar(); } + + default Set matchDeclNames() { + return getParent().matchDeclNames(); + } } private abstract static class Scope { @@ -484,6 +488,11 @@ public WalkContext codeParent() { public boolean isAsync() { return async; } + + @Override + public Set matchDeclNames() { + return Collections.emptySet(); + } } public abstract static class TranslationVisitor extends AbstractParser.CAstVisitor @@ -2363,16 +2372,26 @@ public CAstNode visitMatchValue(PyObject match, WalkContext context) { visit(match.getAttr("value", PyObject.class), context)); } + public CAstNode visitMatchAs(PyObject match, WalkContext context) { + String id = match.getAttr("name", String.class); + context.matchDeclNames().add(id); + return ast.makeNode(CAstNode.BLOCK_EXPR, + ast.makeNode(CAstNode.ASSIGN, + ast.makeNode(CAstNode.VAR, ast.makeConstant(id)), + context.matchVar()), + ast.makeConstant(true)); + } + public CAstNode visitMatch(PyObject match, WalkContext context) { - CAstNode var; - CAstNode exprDecl = + CAstNode exprDecl = ast.makeNode( CAstNode.DECL_STMT, - var = ast.makeConstant(new CAstSymbolImpl("__expr__", CAstType.DYNAMIC)), + ast.makeConstant(new CAstSymbolImpl("__expr__", CAstType.DYNAMIC)), visit(match.getAttr("subject", PyObject.class), context)); @SuppressWarnings("unchecked") java.util.List cases = match.getAttr("cases", List.class); + Set decls = HashSetFactory.make(); WalkContext mc = new WalkContext() { @@ -2383,8 +2402,13 @@ public WalkContext getParent() { @Override public CAstNode matchVar() { - return var; + return ast.makeNode(CAstNode.VAR, ast.makeConstant("__expr__")); } + + @Override + public Set matchDeclNames() { + return decls; + } }; CAstNode body = @@ -2415,6 +2439,16 @@ public CAstNode matchVar() { }) .collect(Collectors.toList())); + if (! decls.isEmpty()) { + exprDecl = ast.makeNode(CAstNode.BLOCK_STMT, + exprDecl, + ast.makeNode(CAstNode.BLOCK_STMT, + decls.stream().map(s -> ast.makeNode( + CAstNode.DECL_STMT, + ast.makeConstant(new CAstSymbolImpl(s, CAstType.DYNAMIC)))) + .collect(Collectors.toList()))); + } + return ast.makeNode(CAstNode.BLOCK_STMT, exprDecl, body); } } diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index 3b3ec12cb..ec80c0ac6 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -1,9 +1,11 @@ package com.ibm.wala.cast.python.cpython.test; +import com.ibm.wala.cast.ipa.callgraph.CAstCallGraphUtil; import com.ibm.wala.cast.python.client.PythonAnalysisEngine; import com.ibm.wala.cast.python.ipa.callgraph.PythonSSAPropagationCallGraphBuilder; import com.ibm.wala.cast.python.test.TestJythonCallGraphShape; import com.ibm.wala.ipa.callgraph.CallGraph; +import com.ibm.wala.ipa.callgraph.propagation.SSAContextInterpreter; import com.ibm.wala.ipa.cha.ClassHierarchyException; import com.ibm.wala.util.CancelException; import java.io.IOException; @@ -22,7 +24,8 @@ public class TestMatch extends TestJythonCallGraphShape { "script match1.py/thursday", "script match1.py/friday", "script match1.py/saturday", - "script match1.py/sunday" + "script match1.py/sunday", + "script match1.py/otherDay" } } }; @@ -33,6 +36,14 @@ public void testMatch1() PythonAnalysisEngine E = makeEngine("match1.py"); PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); CallGraph CG = B.makeCallGraph(B.getOptions()); + + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), + B.getPointerAnalysis(), + CG); + verifyGraphAssertions(CG, assertionsForMatch1); } } From e0cd646af4d99f0c0eb99fb9ccb8f567fbfebb71 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Sun, 7 Sep 2025 18:08:05 -0400 Subject: [PATCH 02/13] spotless --- .../data/match1.py | 4 +- .../jep/ast/CPythonAstToCAstTranslator.java | 62 +++++++++++-------- .../cast/python/cpython/test/TestMatch.java | 6 +- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/core/com.ibm.wala.cast.python.test/data/match1.py b/core/com.ibm.wala.cast.python.test/data/match1.py index 22e652047..679ddcc5d 100644 --- a/core/com.ibm.wala.cast.python.test/data/match1.py +++ b/core/com.ibm.wala.cast.python.test/data/match1.py @@ -25,11 +25,11 @@ def saturday(): def sunday(): print("Sunday") - + def otherDay(): print("other day??") - + for day in [1, 2, 3, 4, 5, 6, 7, otherDay]: match day: case 1: diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 9b5a9b943..820229b19 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -404,9 +404,9 @@ default List inits() { default CAstNode matchVar() { return getParent().matchVar(); } - + default Set matchDeclNames() { - return getParent().matchDeclNames(); + return getParent().matchDeclNames(); } } @@ -489,10 +489,10 @@ public boolean isAsync() { return async; } - @Override - public Set matchDeclNames() { - return Collections.emptySet(); - } + @Override + public Set matchDeclNames() { + return Collections.emptySet(); + } } public abstract static class TranslationVisitor extends AbstractParser.CAstVisitor @@ -2373,17 +2373,19 @@ public CAstNode visitMatchValue(PyObject match, WalkContext context) { } public CAstNode visitMatchAs(PyObject match, WalkContext context) { - String id = match.getAttr("name", String.class); - context.matchDeclNames().add(id); - return ast.makeNode(CAstNode.BLOCK_EXPR, - ast.makeNode(CAstNode.ASSIGN, - ast.makeNode(CAstNode.VAR, ast.makeConstant(id)), - context.matchVar()), - ast.makeConstant(true)); + String id = match.getAttr("name", String.class); + context.matchDeclNames().add(id); + return ast.makeNode( + CAstNode.BLOCK_EXPR, + ast.makeNode( + CAstNode.ASSIGN, + ast.makeNode(CAstNode.VAR, ast.makeConstant(id)), + context.matchVar()), + ast.makeConstant(true)); } public CAstNode visitMatch(PyObject match, WalkContext context) { - CAstNode exprDecl = + CAstNode exprDecl = ast.makeNode( CAstNode.DECL_STMT, ast.makeConstant(new CAstSymbolImpl("__expr__", CAstType.DYNAMIC)), @@ -2405,10 +2407,10 @@ public CAstNode matchVar() { return ast.makeNode(CAstNode.VAR, ast.makeConstant("__expr__")); } - @Override - public Set matchDeclNames() { - return decls; - } + @Override + public Set matchDeclNames() { + return decls; + } }; CAstNode body = @@ -2439,16 +2441,22 @@ public Set matchDeclNames() { }) .collect(Collectors.toList())); - if (! decls.isEmpty()) { - exprDecl = ast.makeNode(CAstNode.BLOCK_STMT, - exprDecl, - ast.makeNode(CAstNode.BLOCK_STMT, - decls.stream().map(s -> ast.makeNode( - CAstNode.DECL_STMT, - ast.makeConstant(new CAstSymbolImpl(s, CAstType.DYNAMIC)))) - .collect(Collectors.toList()))); + if (!decls.isEmpty()) { + exprDecl = + ast.makeNode( + CAstNode.BLOCK_STMT, + exprDecl, + ast.makeNode( + CAstNode.BLOCK_STMT, + decls.stream() + .map( + s -> + ast.makeNode( + CAstNode.DECL_STMT, + ast.makeConstant(new CAstSymbolImpl(s, CAstType.DYNAMIC)))) + .collect(Collectors.toList()))); } - + return ast.makeNode(CAstNode.BLOCK_STMT, exprDecl, body); } } diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index ec80c0ac6..060e0e3b3 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -36,13 +36,11 @@ public void testMatch1() PythonAnalysisEngine E = makeEngine("match1.py"); PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); CallGraph CG = B.makeCallGraph(B.getOptions()); - + System.err.println(CG); CAstCallGraphUtil.AVOID_DUMP.set(false); CAstCallGraphUtil.dumpCG( - (SSAContextInterpreter) B.getContextInterpreter(), - B.getPointerAnalysis(), - CG); + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); verifyGraphAssertions(CG, assertionsForMatch1); } From 4bf8b9c7aaa1f91edc9d02ffe1f7a457b57982cb Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Mon, 8 Sep 2025 10:39:00 -0400 Subject: [PATCH 03/13] spotless --- .../jep/ast/CPythonAstToCAstTranslator.java | 4 +- .../cast/python/cpython/test/TestMatch.java | 101 ++++++++++++------ 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 820229b19..6d903de34 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -1435,8 +1435,10 @@ public CAstNode visitCompare(PyObject cmp, WalkContext context) { CAstNode op = translateOperator( ops.next().getAttr("__class__", PyObject.class).getAttr("__name__", String.class)); - CAstNode rhs = visit(exprs.next(), context); + PyObject exp = exprs.next(); + CAstNode rhs = visit(exp, context); CAstNode cmpop = ast.makeNode(CAstNode.BINARY_EXPR, op, ln, rhs); + ln = visit(exp, context); expr = expr == null ? cmpop diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index 060e0e3b3..431fae736 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -12,36 +12,73 @@ import org.junit.Test; public class TestMatch extends TestJythonCallGraphShape { - protected static final Object[][] assertionsForMatch1 = - new Object[][] { - new Object[] {ROOT, new String[] {"script match1.py"}}, - new Object[] { - "script match1.py", - new String[] { - "script match1.py/monday", - "script match1.py/tuesday", - "script match1.py/wednesday", - "script match1.py/thursday", - "script match1.py/friday", - "script match1.py/saturday", - "script match1.py/sunday", - "script match1.py/otherDay" - } - } - }; - - @Test - public void testMatch1() - throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { - PythonAnalysisEngine E = makeEngine("match1.py"); - PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); - CallGraph CG = B.makeCallGraph(B.getOptions()); - - System.err.println(CG); - CAstCallGraphUtil.AVOID_DUMP.set(false); - CAstCallGraphUtil.dumpCG( - (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); - - verifyGraphAssertions(CG, assertionsForMatch1); - } + protected static final Object[][] assertionsForMatch1 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match1.py"}}, + new Object[] { + "script match1.py", + new String[] { + "script match1.py/monday", + "script match1.py/tuesday", + "script match1.py/wednesday", + "script match1.py/thursday", + "script match1.py/friday", + "script match1.py/saturday", + "script match1.py/sunday", + "script match1.py/otherDay" + } + } + }; + + @Test + public void testMatch1() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match1.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); + + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + + verifyGraphAssertions(CG, assertionsForMatch1); + } + + protected static final Object[][] assertionsForMatch2 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match2.py"}}, + new Object[] { + "script match2.py", + new String[] { + "script match2.py/doit"}}, + new Object[] { + "script match2.py/doit", + new String[] { + "script match2.py/monday", + "script match2.py/tuesday", + "script match2.py/wednesday", + "script match2.py/thursday", + "script match2.py/friday", + "script match2.py/saturday", + "script match2.py/sunday", + "script match2.py/otherDay" + } + } + }; + + @Test + public void testMatch2() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match2.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); + + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + + verifyGraphAssertions(CG, assertionsForMatch2); + } } From d760d598c14307090e6e01e28f262be3fb3a5e06 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Mon, 8 Sep 2025 10:40:53 -0400 Subject: [PATCH 04/13] 1) more tests for match 2) fix to visitCompare --- .../jep/ast/CPythonAstToCAstTranslator.java | 2 +- .../cast/python/cpython/test/TestMatch.java | 121 +++++++++--------- 2 files changed, 60 insertions(+), 63 deletions(-) diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 6d903de34..264a0516d 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -1436,7 +1436,7 @@ public CAstNode visitCompare(PyObject cmp, WalkContext context) { translateOperator( ops.next().getAttr("__class__", PyObject.class).getAttr("__name__", String.class)); PyObject exp = exprs.next(); - CAstNode rhs = visit(exp, context); + CAstNode rhs = visit(exp, context); CAstNode cmpop = ast.makeNode(CAstNode.BINARY_EXPR, op, ln, rhs); ln = visit(exp, context); expr = diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index 431fae736..232f43409 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -12,73 +12,70 @@ import org.junit.Test; public class TestMatch extends TestJythonCallGraphShape { - protected static final Object[][] assertionsForMatch1 = - new Object[][] { - new Object[] {ROOT, new String[] {"script match1.py"}}, - new Object[] { - "script match1.py", - new String[] { - "script match1.py/monday", - "script match1.py/tuesday", - "script match1.py/wednesday", - "script match1.py/thursday", - "script match1.py/friday", - "script match1.py/saturday", - "script match1.py/sunday", - "script match1.py/otherDay" - } - } - }; + protected static final Object[][] assertionsForMatch1 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match1.py"}}, + new Object[] { + "script match1.py", + new String[] { + "script match1.py/monday", + "script match1.py/tuesday", + "script match1.py/wednesday", + "script match1.py/thursday", + "script match1.py/friday", + "script match1.py/saturday", + "script match1.py/sunday", + "script match1.py/otherDay" + } + } + }; - @Test - public void testMatch1() - throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { - PythonAnalysisEngine E = makeEngine("match1.py"); - PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); - CallGraph CG = B.makeCallGraph(B.getOptions()); + @Test + public void testMatch1() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match1.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); - System.err.println(CG); - CAstCallGraphUtil.AVOID_DUMP.set(false); - CAstCallGraphUtil.dumpCG( - (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); - verifyGraphAssertions(CG, assertionsForMatch1); - } + verifyGraphAssertions(CG, assertionsForMatch1); + } - protected static final Object[][] assertionsForMatch2 = - new Object[][] { - new Object[] {ROOT, new String[] {"script match2.py"}}, - new Object[] { - "script match2.py", - new String[] { - "script match2.py/doit"}}, - new Object[] { - "script match2.py/doit", - new String[] { - "script match2.py/monday", - "script match2.py/tuesday", - "script match2.py/wednesday", - "script match2.py/thursday", - "script match2.py/friday", - "script match2.py/saturday", - "script match2.py/sunday", - "script match2.py/otherDay" - } - } - }; + protected static final Object[][] assertionsForMatch2 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match2.py"}}, + new Object[] {"script match2.py", new String[] {"script match2.py/doit"}}, + new Object[] { + "script match2.py/doit", + new String[] { + "script match2.py/monday", + "script match2.py/tuesday", + "script match2.py/wednesday", + "script match2.py/thursday", + "script match2.py/friday", + "script match2.py/saturday", + "script match2.py/sunday", + "script match2.py/otherDay" + } + } + }; - @Test - public void testMatch2() - throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { - PythonAnalysisEngine E = makeEngine("match2.py"); - PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); - CallGraph CG = B.makeCallGraph(B.getOptions()); + @Test + public void testMatch2() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match2.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); - System.err.println(CG); - CAstCallGraphUtil.AVOID_DUMP.set(false); - CAstCallGraphUtil.dumpCG( - (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); - verifyGraphAssertions(CG, assertionsForMatch2); - } + verifyGraphAssertions(CG, assertionsForMatch2); + } } From 0d00c143c995bff9e3c81dd9300d7f589458f491 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Mon, 8 Sep 2025 10:52:11 -0400 Subject: [PATCH 05/13] add forgotten test file --- .../data/match2.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 core/com.ibm.wala.cast.python.test/data/match2.py diff --git a/core/com.ibm.wala.cast.python.test/data/match2.py b/core/com.ibm.wala.cast.python.test/data/match2.py new file mode 100644 index 000000000..8c51b7991 --- /dev/null +++ b/core/com.ibm.wala.cast.python.test/data/match2.py @@ -0,0 +1,55 @@ +def monday(): + print("Monday") + + +def tuesday(): + print("Tuesday") + + +def wednesday(): + print("Wednesday") + + +def thursday(): + print("Thursday") + + +def friday(): + print("Friday") + + +def saturday(): + print("Saturday") + + +def sunday(): + print("Sunday") + + +def otherDay(): + print("other day??") + + +def doit(month): + for day in [1, 2, 3, 4, 5, 6, 7, otherDay]: + match day: + case 1 if 1 <= month <= 12: + monday() + case 2 if 1 <= month <= 12: + tuesday() + case 3 if 1 <= month <= 12: + wednesday() + case 4 if 1 <= month <= 12: + thursday() + case 5 if 1 <= month <= 12: + friday() + case 6 if 1 <= month <= 12: + saturday() + case 7 if 1 <= month <= 12: + sunday() + case x if callable(x): + x() + + +doit(4) +doit(0) From 4e38bd02ba5c52bc9d77e5347125d154058dd391 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Fri, 19 Sep 2025 16:42:41 -0400 Subject: [PATCH 06/13] first cut at implementing | for match statements --- .../jep/ast/CPythonAstToCAstTranslator.java | 19 ++++++++++++ .../cast/python/cpython/test/TestMatch.java | 30 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 264a0516d..153991d1b 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -2386,6 +2386,25 @@ public CAstNode visitMatchAs(PyObject match, WalkContext context) { ast.makeConstant(true)); } + public CAstNode visitMatchOr(PyObject match, WalkContext context) { + @SuppressWarnings("unchecked") + java.util.List patterns = match.getAttr("patterns", List.class); + return patterns.stream().map(o -> visit(o, context)).reduce((l, rhs) -> { + CAstNode lhs = + ast.makeNode( + CAstNode.DECL_STMT, + ast.makeConstant(new CAstSymbolImpl("__lhs__", CAstType.DYNAMIC)), + l); + return + ast.makeNode(CAstNode.BLOCK_EXPR, + lhs, + ast.makeNode(CAstNode.IF_EXPR, + ast.makeNode(CAstNode.VAR, ast.makeConstant("__lhs__")), + ast.makeNode(CAstNode.VAR, ast.makeConstant("__lhs__")), + rhs)); + }).get(); + } + public CAstNode visitMatch(PyObject match, WalkContext context) { CAstNode exprDecl = ast.makeNode( diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index 232f43409..c490b243c 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -78,4 +78,34 @@ public void testMatch2() verifyGraphAssertions(CG, assertionsForMatch2); } + + protected static final Object[][] assertionsForMatch3 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match3.py"}}, + new Object[] {"script match3.py", new String[] {"script match3.py/doit"}}, + new Object[] { + "script match3.py/doit", + new String[] { + "script match3.py/weekday", + "script match3.py/weekend", + "script match3.py/otherDay" + } + } + }; + + @Test + public void testMatch3() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match3.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); + + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + + verifyGraphAssertions(CG, assertionsForMatch3); + } + } From 5f46cdb8cd91c77d068306bb864641697a0643d4 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Fri, 19 Sep 2025 16:43:27 -0400 Subject: [PATCH 07/13] test for match | --- .../data/match3.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 core/com.ibm.wala.cast.python.test/data/match3.py diff --git a/core/com.ibm.wala.cast.python.test/data/match3.py b/core/com.ibm.wala.cast.python.test/data/match3.py new file mode 100644 index 000000000..f795124cc --- /dev/null +++ b/core/com.ibm.wala.cast.python.test/data/match3.py @@ -0,0 +1,25 @@ +def weekday(): + print("week day") + + +def weekend(): + print("week end") + + +def otherDay(): + print("other day??") + + +def doit(month): + for day in [1, 2, 3, 4, 5, 6, 7, otherDay]: + match day: + case 1 | 2 | 3 | 4 | 5 if 1 <= month <= 12: + weekday() + case 6 | 7 if 1 <= month <= 12: + weekend() + case x if callable(x): + x() + + +doit(4) +doit(0) From 7fc49bc409fc17208351dccf6a59d45fdfc7cb10 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Fri, 19 Sep 2025 16:49:53 -0400 Subject: [PATCH 08/13] spotless --- .../jep/ast/CPythonAstToCAstTranslator.java | 33 +++++++------ .../cast/python/cpython/test/TestMatch.java | 49 +++++++++---------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 153991d1b..0d8bc0573 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -2387,24 +2387,29 @@ public CAstNode visitMatchAs(PyObject match, WalkContext context) { } public CAstNode visitMatchOr(PyObject match, WalkContext context) { - @SuppressWarnings("unchecked") - java.util.List patterns = match.getAttr("patterns", List.class); - return patterns.stream().map(o -> visit(o, context)).reduce((l, rhs) -> { - CAstNode lhs = + @SuppressWarnings("unchecked") + java.util.List patterns = match.getAttr("patterns", List.class); + return patterns.stream() + .map(o -> visit(o, context)) + .reduce( + (l, rhs) -> { + CAstNode lhs = ast.makeNode( CAstNode.DECL_STMT, ast.makeConstant(new CAstSymbolImpl("__lhs__", CAstType.DYNAMIC)), l); - return - ast.makeNode(CAstNode.BLOCK_EXPR, - lhs, - ast.makeNode(CAstNode.IF_EXPR, - ast.makeNode(CAstNode.VAR, ast.makeConstant("__lhs__")), - ast.makeNode(CAstNode.VAR, ast.makeConstant("__lhs__")), - rhs)); - }).get(); - } - + return ast.makeNode( + CAstNode.BLOCK_EXPR, + lhs, + ast.makeNode( + CAstNode.IF_EXPR, + ast.makeNode(CAstNode.VAR, ast.makeConstant("__lhs__")), + ast.makeNode(CAstNode.VAR, ast.makeConstant("__lhs__")), + rhs)); + }) + .get(); + } + public CAstNode visitMatch(PyObject match, WalkContext context) { CAstNode exprDecl = ast.makeNode( diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index c490b243c..3d5a6cf6e 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -78,34 +78,31 @@ public void testMatch2() verifyGraphAssertions(CG, assertionsForMatch2); } - - protected static final Object[][] assertionsForMatch3 = - new Object[][] { - new Object[] {ROOT, new String[] {"script match3.py"}}, - new Object[] {"script match3.py", new String[] {"script match3.py/doit"}}, - new Object[] { - "script match3.py/doit", - new String[] { - "script match3.py/weekday", - "script match3.py/weekend", - "script match3.py/otherDay" - } - } - }; - @Test - public void testMatch3() - throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { - PythonAnalysisEngine E = makeEngine("match3.py"); - PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); - CallGraph CG = B.makeCallGraph(B.getOptions()); + protected static final Object[][] assertionsForMatch3 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match3.py"}}, + new Object[] {"script match3.py", new String[] {"script match3.py/doit"}}, + new Object[] { + "script match3.py/doit", + new String[] { + "script match3.py/weekday", "script match3.py/weekend", "script match3.py/otherDay" + } + } + }; - System.err.println(CG); - CAstCallGraphUtil.AVOID_DUMP.set(false); - CAstCallGraphUtil.dumpCG( - (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + @Test + public void testMatch3() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match3.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); - verifyGraphAssertions(CG, assertionsForMatch3); - } + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + verifyGraphAssertions(CG, assertionsForMatch3); + } } From c4f4dc47c81316c931d99ccf5ab201539c21e038 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Sat, 20 Sep 2025 22:06:46 -0400 Subject: [PATCH 09/13] match default --- .../data/match3.py | 6 ++++++ .../jep/ast/CPythonAstToCAstTranslator.java | 20 +++++++++++-------- .../cast/python/cpython/test/TestMatch.java | 5 ++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/core/com.ibm.wala.cast.python.test/data/match3.py b/core/com.ibm.wala.cast.python.test/data/match3.py index f795124cc..f00e18fd8 100644 --- a/core/com.ibm.wala.cast.python.test/data/match3.py +++ b/core/com.ibm.wala.cast.python.test/data/match3.py @@ -10,6 +10,10 @@ def otherDay(): print("other day??") +def somethingElse(): + print("something else") + + def doit(month): for day in [1, 2, 3, 4, 5, 6, 7, otherDay]: match day: @@ -19,6 +23,8 @@ def doit(month): weekend() case x if callable(x): x() + case _: + somethingElse() doit(4) diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index 0d8bc0573..ce21351b4 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -2376,14 +2376,18 @@ public CAstNode visitMatchValue(PyObject match, WalkContext context) { public CAstNode visitMatchAs(PyObject match, WalkContext context) { String id = match.getAttr("name", String.class); - context.matchDeclNames().add(id); - return ast.makeNode( - CAstNode.BLOCK_EXPR, - ast.makeNode( - CAstNode.ASSIGN, - ast.makeNode(CAstNode.VAR, ast.makeConstant(id)), - context.matchVar()), - ast.makeConstant(true)); + if (id == null) { + return ast.makeConstant(true); + } else { + context.matchDeclNames().add(id); + return ast.makeNode( + CAstNode.BLOCK_EXPR, + ast.makeNode( + CAstNode.ASSIGN, + ast.makeNode(CAstNode.VAR, ast.makeConstant(id)), + context.matchVar()), + ast.makeConstant(true)); + } } public CAstNode visitMatchOr(PyObject match, WalkContext context) { diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index 3d5a6cf6e..d5bbd8721 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -86,7 +86,10 @@ public void testMatch2() new Object[] { "script match3.py/doit", new String[] { - "script match3.py/weekday", "script match3.py/weekend", "script match3.py/otherDay" + "script match3.py/weekday", + "script match3.py/weekend", + "script match3.py/otherDay", + "script match3.py/somethingElse" } } }; From 8d308d713f8b9b860017eeeac4e942cdc2cadee1 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Tue, 7 Oct 2025 09:58:12 -0400 Subject: [PATCH 10/13] spotless --- WALA | 2 +- .../data/match3.py | 16 +++--- .../data/match4.py | 41 ++++++++++++++ .../python/ir/PythonCAstToIRTranslator.java | 25 +++++++++ .../.launchers/PassingSuite.launch | 3 +- jep/com.ibm.wala.cast.python.cpython/pom.xml | 3 -- .../jep/ast/CPythonAstToCAstTranslator.java | 53 ++++++++++++++++++- .../cast/python/cpython/test/TestMatch.java | 32 +++++++++++ pom.xml | 2 +- 9 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 core/com.ibm.wala.cast.python.test/data/match4.py diff --git a/WALA b/WALA index 6a3aa47bb..e4f5be297 160000 --- a/WALA +++ b/WALA @@ -1 +1 @@ -Subproject commit 6a3aa47bb773734b7c4b9d8787f5b75edd136ed4 +Subproject commit e4f5be2973d183634ea201ade5aae7cab8d8165a diff --git a/core/com.ibm.wala.cast.python.test/data/match3.py b/core/com.ibm.wala.cast.python.test/data/match3.py index f00e18fd8..3344e40f4 100644 --- a/core/com.ibm.wala.cast.python.test/data/match3.py +++ b/core/com.ibm.wala.cast.python.test/data/match3.py @@ -1,9 +1,9 @@ -def weekday(): - print("week day") +def weekday(day): + print("week day " + str(day)) -def weekend(): - print("week end") +def weekend(day): + print("week end " + str(day)) def otherDay(): @@ -17,10 +17,10 @@ def somethingElse(): def doit(month): for day in [1, 2, 3, 4, 5, 6, 7, otherDay]: match day: - case 1 | 2 | 3 | 4 | 5 if 1 <= month <= 12: - weekday() - case 6 | 7 if 1 <= month <= 12: - weekend() + case 1 | 2 | 3 | 4 | 5 as day if 1 <= month <= 12: + weekday(day) + case 6 | 7 as day if 1 <= month <= 12: + weekend(day) case x if callable(x): x() case _: diff --git a/core/com.ibm.wala.cast.python.test/data/match4.py b/core/com.ibm.wala.cast.python.test/data/match4.py new file mode 100644 index 000000000..8af5fe130 --- /dev/null +++ b/core/com.ibm.wala.cast.python.test/data/match4.py @@ -0,0 +1,41 @@ +def weekday(day): + print("week day " + str(day)) + + +def weekend(day): + print("week end " + str(day)) + + +def otherDay(): + print("other day??") + + +def somethingElse(): + print("something else") + + +def mten(): + return 10 + + +def mseven(): + return 10 + + +def doit(dmf): + match dmf: + case [1 | 2 | 3 | 4 | 5 as day, month] if 1 <= month() <= 12: + weekday(day) + case [6 | 7 as day, month] if 1 <= month() <= 12: + weekend(day) + case x if callable(x): + x() + case _: + somethingElse() + + +doit((3, mseven)) +doit([7, mten]) +doit([8, mten]) +doit(otherDay) +doit(0) diff --git a/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ir/PythonCAstToIRTranslator.java b/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ir/PythonCAstToIRTranslator.java index 295346f0a..f5b2071d5 100644 --- a/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ir/PythonCAstToIRTranslator.java +++ b/core/com.ibm.wala.cast.python/source/com/ibm/wala/cast/python/ir/PythonCAstToIRTranslator.java @@ -20,6 +20,7 @@ import com.ibm.wala.cast.ir.ssa.AstGlobalRead; import com.ibm.wala.cast.ir.ssa.AstInstructionFactory; import com.ibm.wala.cast.ir.translator.AstTranslator; +import com.ibm.wala.cast.ir.translator.AstTranslator.WalkContext; import com.ibm.wala.cast.loader.AstMethod.DebuggingInformation; import com.ibm.wala.cast.loader.DynamicCallSiteReference; import com.ibm.wala.cast.python.loader.DynamicAnnotatableEntity; @@ -1232,4 +1233,28 @@ protected void leaveBlockStmt(CAstNode n, WalkContext c, CAstVisitor visitor) { + int result = context.currentScope().allocateTempValue(); + context.setValue(n, result); + return false; + } + + @Override + protected void leaveInstanceOf( + CAstNode n, WalkContext context, CAstVisitor visitor) { + int result = context.getValue(n); + TypeReference ref = (TypeReference) n.getChild(0).getValue(); + + context + .cfg() + .addInstruction( + insts.InstanceofInstruction( + context.cfg().getCurrentInstruction(), + result, + context.getValue(n.getChild(1)), + ref)); + } } diff --git a/jep/com.ibm.wala.cast.python.cpython/.launchers/PassingSuite.launch b/jep/com.ibm.wala.cast.python.cpython/.launchers/PassingSuite.launch index 5e5a4a6bc..671f91348 100644 --- a/jep/com.ibm.wala.cast.python.cpython/.launchers/PassingSuite.launch +++ b/jep/com.ibm.wala.cast.python.cpython/.launchers/PassingSuite.launch @@ -7,6 +7,7 @@ + @@ -31,5 +32,5 @@ - + diff --git a/jep/com.ibm.wala.cast.python.cpython/pom.xml b/jep/com.ibm.wala.cast.python.cpython/pom.xml index 9c0e9239f..3adb89f83 100644 --- a/jep/com.ibm.wala.cast.python.cpython/pom.xml +++ b/jep/com.ibm.wala.cast.python.cpython/pom.xml @@ -7,9 +7,6 @@ 0.0.1-SNAPSHOT com.ibm.wala.cast.python.cpython - - 1.6.12-SNAPSHOT - com.ibm.wala diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index ce21351b4..bdd2e9d7c 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -13,6 +13,7 @@ import com.ibm.wala.cast.python.ir.PythonCAstToIRTranslator; import com.ibm.wala.cast.python.ir.PythonLanguage; import com.ibm.wala.cast.python.jep.Util; +import com.ibm.wala.cast.python.jep.ast.CPythonAstToCAstTranslator.WalkContext; import com.ibm.wala.cast.python.loader.JepPythonLoaderFactory; import com.ibm.wala.cast.python.loader.PythonLoaderFactory; import com.ibm.wala.cast.python.parser.AbstractParser; @@ -2376,8 +2377,22 @@ public CAstNode visitMatchValue(PyObject match, WalkContext context) { public CAstNode visitMatchAs(PyObject match, WalkContext context) { String id = match.getAttr("name", String.class); + PyObject pattern = match.getAttr("pattern", PyObject.class); if (id == null) { return ast.makeConstant(true); + } else if (pattern != null) { + context.matchDeclNames().add(id); + return ast.makeNode( + CAstNode.IF_EXPR, + visit(pattern, context), + ast.makeNode( + CAstNode.BLOCK_EXPR, + ast.makeNode( + CAstNode.ASSIGN, + ast.makeNode(CAstNode.VAR, ast.makeConstant(id)), + context.matchVar()), + ast.makeConstant(true)), + ast.makeConstant(false)); } else { context.matchDeclNames().add(id); return ast.makeNode( @@ -2414,11 +2429,45 @@ public CAstNode visitMatchOr(PyObject match, WalkContext context) { .get(); } + public CAstNode visitMatchSequence(PyObject seq, WalkContext context) { + @SuppressWarnings("unchecked") + java.util.List patterns = seq.getAttr("patterns", List.class); + int i = 0; + CAstNode result = + ast.makeNode( + CAstNode.INSTANCEOF, ast.makeConstant(PythonTypes.sequence), context.matchVar()); + for (PyObject p : patterns) { + int idx = i; + WalkContext eltContext = + new WalkContext() { + @Override + public WalkContext getParent() { + return context; + } + + @Override + public CAstNode matchVar() { + return ast.makeNode(CAstNode.OBJECT_REF, context.matchVar(), ast.makeConstant(idx)); + } + }; + + CAstNode eltNode = visit(p, eltContext); + + result = ast.makeNode(CAstNode.IF_EXPR, result, eltNode, ast.makeConstant(false)); + i++; + } + + return result; + } + + private int nextDecl = 0; + public CAstNode visitMatch(PyObject match, WalkContext context) { + String exprVarName = "__expr" + nextDecl++ + "__"; CAstNode exprDecl = ast.makeNode( CAstNode.DECL_STMT, - ast.makeConstant(new CAstSymbolImpl("__expr__", CAstType.DYNAMIC)), + ast.makeConstant(new CAstSymbolImpl(exprVarName, CAstType.DYNAMIC)), visit(match.getAttr("subject", PyObject.class), context)); @SuppressWarnings("unchecked") java.util.List cases = match.getAttr("cases", List.class); @@ -2434,7 +2483,7 @@ public WalkContext getParent() { @Override public CAstNode matchVar() { - return ast.makeNode(CAstNode.VAR, ast.makeConstant("__expr__")); + return ast.makeNode(CAstNode.VAR, ast.makeConstant(exprVarName)); } @Override diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index d5bbd8721..233a6a810 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -108,4 +108,36 @@ public void testMatch3() verifyGraphAssertions(CG, assertionsForMatch3); } + + protected static final Object[][] assertionsForMatch4 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match4.py"}}, + new Object[] {"script match4.py", new String[] {"script match4.py/doit"}}, + new Object[] { + "script match4.py/doit", + new String[] { + "script match4.py/mseven", + "script match4.py/mten", + "script match4.py/weekday", + "script match4.py/weekend", + "script match4.py/otherDay", + "script match4.py/somethingElse" + } + } + }; + + @Test + public void testMatch4() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match4.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); + + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + + verifyGraphAssertions(CG, assertionsForMatch4); + } } diff --git a/pom.xml b/pom.xml index ae40da0d4..cc7137d9d 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 3.14.0 UTF-8 b000 - 1.6.12-SNAPSHOT + 1.6.13-SNAPSHOT 2.46.1 3.5.3 both From 3afde3ed4907a03ce5a55057bc0a6fe92f656f2f Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Tue, 4 Nov 2025 08:56:19 -0500 Subject: [PATCH 11/13] initial work on pattern matching classes --- .../data/match5.py | 37 +++++++++++++++++++ .../jep/ast/CPythonAstToCAstTranslator.java | 34 +++++++++++++++++ .../cast/python/cpython/test/TestMatch.java | 35 ++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 core/com.ibm.wala.cast.python.test/data/match5.py diff --git a/core/com.ibm.wala.cast.python.test/data/match5.py b/core/com.ibm.wala.cast.python.test/data/match5.py new file mode 100644 index 000000000..7ab1f2048 --- /dev/null +++ b/core/com.ibm.wala.cast.python.test/data/match5.py @@ -0,0 +1,37 @@ +class Nothing: + def act(self): + print("Nothing") + + +def id(x): + return x + + +class Something: + a = 0 + b = 0 + + def __init__(self, a, b): + self.a = id(a) + self.b = id(b) + + def act(self): + print(self.a + self.b) + + +def doit(x): + match x: + case Nothing(): + x.act() + case Something(a=5, b=7): + x.act() + case Something(): + print("unexpected something") + case _: + print("unexpected value") + + +doit(Nothing()) +doit(Something(5, 7)) +doit(Something(3, 4)) +doit(10) diff --git a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java index bdd2e9d7c..7f679a264 100644 --- a/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java +++ b/jep/com.ibm.wala.cast.python.cpython/source/com/ibm/wala/cast/python/jep/ast/CPythonAstToCAstTranslator.java @@ -2460,6 +2460,40 @@ public CAstNode matchVar() { return result; } + public CAstNode visitMatchClass(PyObject cls, WalkContext context) { + PyObject klass = cls.getAttr("cls", PyObject.class); + CAstNode result = + ast.makeNode(CAstNode.INSTANCEOF, visit(klass, context), context.matchVar()); + + @SuppressWarnings("unchecked") + Iterator names = cls.getAttr("kwd_attrs", List.class).iterator(); + @SuppressWarnings("unchecked") + Iterator patterns = cls.getAttr("kwd_patterns", List.class).iterator(); + while (names.hasNext()) { + String name = names.next(); + WalkContext eltContext = + new WalkContext() { + + @Override + public WalkContext getParent() { + return context; + } + + @Override + public CAstNode matchVar() { + return ast.makeNode( + CAstNode.OBJECT_REF, context.matchVar(), ast.makeConstant(name)); + } + }; + + CAstNode eltNode = visit(patterns.next(), eltContext); + + result = ast.makeNode(CAstNode.IF_EXPR, result, eltNode, ast.makeConstant(false)); + } + + return result; + } + private int nextDecl = 0; public CAstNode visitMatch(PyObject match, WalkContext context) { diff --git a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java index 233a6a810..c1eff2380 100644 --- a/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java +++ b/jep/com.ibm.wala.cast.python.cpython/test/com/ibm/wala/cast/python/cpython/test/TestMatch.java @@ -140,4 +140,39 @@ public void testMatch4() verifyGraphAssertions(CG, assertionsForMatch4); } + + protected static final Object[][] assertionsForMatch5 = + new Object[][] { + new Object[] {ROOT, new String[] {"script match5.py"}}, + new Object[] {"script match5.py", new String[] {"script match5.py/doit"}}, + new Object[] { + "script match5.py/doit", + new String[] { + "$script match5.py/Nothing/act:trampoline1", + "$script match5.py/Something/act:trampoline1", + } + }, + new Object[] { + "$script match5.py/Nothing/act:trampoline1", new String[] {"script match5.py/Nothing/act"} + }, + new Object[] { + "$script match5.py/Something/act:trampoline1", + new String[] {"script match5.py/Something/act"} + } + }; + + @Test + public void testMatch5() + throws ClassHierarchyException, IllegalArgumentException, CancelException, IOException { + PythonAnalysisEngine E = makeEngine("match5.py"); + PythonSSAPropagationCallGraphBuilder B = E.defaultCallGraphBuilder(); + CallGraph CG = B.makeCallGraph(B.getOptions()); + + System.err.println(CG); + CAstCallGraphUtil.AVOID_DUMP.set(false); + CAstCallGraphUtil.dumpCG( + (SSAContextInterpreter) B.getContextInterpreter(), B.getPointerAnalysis(), CG); + + verifyGraphAssertions(CG, assertionsForMatch5); + } } From 5fde30562a3e4dd21c8570b8c01a3f942e07c233 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Tue, 4 Nov 2025 14:08:27 -0500 Subject: [PATCH 12/13] add wrapper for verifying CG edges --- .../python/test/TestPythonCallGraphShape.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java b/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java index 352fc5475..04be7bb0a 100644 --- a/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java +++ b/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java @@ -2,6 +2,17 @@ import static java.util.Collections.emptyList; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + import com.ibm.wala.cast.python.client.PythonAnalysisEngine; import com.ibm.wala.cast.python.types.PythonTypes; import com.ibm.wala.cast.types.AstMethodReference; @@ -18,15 +29,6 @@ import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.CancelException; import com.ibm.wala.util.collections.HashSetFactory; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Collection; -import java.util.List; -import java.util.Set; public abstract class TestPythonCallGraphShape extends TestCallGraphShape { @@ -130,4 +132,12 @@ StringBuffer dump(CallGraph CG) { } return sb; } + + protected void verifyGraphAssertions(CallGraph CG, Object[][] data) { + List asserts = new ArrayList<>(); + for(int i = 0; i < data.length; i++) { + asserts.add(new GraphAssertion(data[i][0], (String[]) data[i][1])); + } + verifyGraphAssertions(CG, asserts); + } } From e43ae871e6e93d3d2211c7bdc97100c87723e736 Mon Sep 17 00:00:00 2001 From: Julian Dolby Date: Tue, 4 Nov 2025 14:11:39 -0500 Subject: [PATCH 13/13] spotless --- .../python/test/TestPythonCallGraphShape.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java b/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java index 04be7bb0a..fba4a0840 100644 --- a/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java +++ b/core/com.ibm.wala.cast.python.test/source/com/ibm/wala/cast/python/test/TestPythonCallGraphShape.java @@ -2,17 +2,6 @@ import static java.util.Collections.emptyList; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; - import com.ibm.wala.cast.python.client.PythonAnalysisEngine; import com.ibm.wala.cast.python.types.PythonTypes; import com.ibm.wala.cast.types.AstMethodReference; @@ -29,6 +18,16 @@ import com.ibm.wala.types.TypeReference; import com.ibm.wala.util.CancelException; import com.ibm.wala.util.collections.HashSetFactory; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; public abstract class TestPythonCallGraphShape extends TestCallGraphShape { @@ -132,12 +131,12 @@ StringBuffer dump(CallGraph CG) { } return sb; } - + protected void verifyGraphAssertions(CallGraph CG, Object[][] data) { - List asserts = new ArrayList<>(); - for(int i = 0; i < data.length; i++) { - asserts.add(new GraphAssertion(data[i][0], (String[]) data[i][1])); - } - verifyGraphAssertions(CG, asserts); + List asserts = new ArrayList<>(); + for (int i = 0; i < data.length; i++) { + asserts.add(new GraphAssertion(data[i][0], (String[]) data[i][1])); + } + verifyGraphAssertions(CG, asserts); } }