changeset 504:eb7d9538171f

Support for assignments with too many terms on the right-hand side
author tehtmi
date Wed, 20 Sep 2023 01:25:14 -0700
parents cc131a3e5839
children 03ca7f9bd5a2
files src/unluac/decompile/Code.java src/unluac/decompile/Decompiler.java src/unluac/decompile/Op.java src/unluac/decompile/block/IfThenEndBlock.java src/unluac/decompile/statement/Assignment.java src/unluac/test/LuaSpec.java src/unluac/test/TestFiles.java src/unluac/test/TestSuite.java test/src/booleanassign30.lua test/src/excess01.lua test/src/excess02.lua test/src/excess03.lua
diffstat 12 files changed, 192 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/src/unluac/decompile/Code.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/decompile/Code.java	Wed Sep 20 01:25:14 2023 -0700
@@ -142,6 +142,10 @@
     return line + 1 + op(line).jumpField(codepoint(line), extractor);
   }
   
+  public int register(int line) {
+    return op(line).target(codepoint(line), extractor);
+  }
+  
   /**
    * Returns the full instruction codepoint at the given line.
    */
--- a/src/unluac/decompile/Decompiler.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/decompile/Decompiler.java	Wed Sep 20 01:25:14 2023 -0700
@@ -743,7 +743,14 @@
         break;
       case VARARG: {
         boolean multiple = (B != 2);
-        if(B == 1) throw new IllegalStateException();
+        
+        // B == 1 means no registers are set; this should only happen when the VARARG
+        // appears on the right-hand side of an assignment without enough targets.
+        // Should be multiple (as not adjusted "(...)"), and we need to pretend it's
+        // an actual operation so we can capture it...
+        // (luac allocates stack space even though it doesn't technically use it)
+        if(B == 1) B = 2;
+        
         if(B == 0) B = registers - A + 1;
         Expression value = new Vararg(B - 1, multiple);
         operations.add(new MultipleRegisterSet(line, A, A + B - 2, value));
@@ -751,7 +758,7 @@
       }
       case VARARG54: {
         boolean multiple = (C != 2);
-        if(C == 1) throw new IllegalStateException();
+        if(C == 1) C = 2; // see above
         if(C == 0) C = registers - A + 1;
         Expression value = new Vararg(C - 1, multiple);
         operations.add(new MultipleRegisterSet(line, A, A + C - 2, value));
@@ -813,6 +820,16 @@
             break;
           }
         }
+        if(line >= 2 && isMoveIntoTarget(r, line)) {
+          int lastUsed = getMoveIntoTargetValueRegister(r, line, line);
+          int lastLoaded = getRegister(line - 1);
+          if(!r.isLocal(lastLoaded, line - 1)) {
+            while(lastUsed < lastLoaded) {
+              lastUsed++;
+              assign.addExcessValue(r.getValue(lastUsed, line), r.getUpdated(lastUsed, line), lastUsed);
+            }
+          }
+        }
       }
     }
     for(Statement stmt : stmts) {
@@ -960,11 +977,23 @@
             }
           }
         }
-          
+        
+        boolean firstProcess = !assignment.isDeclaration();
         assignment.declare(locals.get(0).begin);
+        int lastAssigned = locals.get(0).register;
         for(Declaration decl : locals) {
           if((scopeEnd == -1 || decl.end == scopeEnd) && decl.register >= block.closeRegister) {
             assignment.addLast(new VariableTarget(decl), r.getValue(decl.register, line + 1), r.getUpdated(decl.register, line - 1));
+            lastAssigned = decl.register;
+          }
+        }
+        
+        if(firstProcess) {
+          // only populate once (excess detection can fail on later lines)
+          int lastLoaded = getRegister(line);
+          while(lastAssigned < lastLoaded) {
+            lastAssigned++;
+            assignment.addExcessValue(r.getValue(lastAssigned, line + 1), r.getUpdated(lastAssigned, line), lastAssigned);
           }
         }
       }
@@ -1067,4 +1096,43 @@
     }
   }
   
+  private int getMoveIntoTargetValueRegister(Registers r, int line, int previous) {
+    int A = code.A(line);
+    int B = code.B(line);
+    int C = code.C(line);
+    switch(code.op(line)) {
+      case MOVE:
+        return B;
+      case SETUPVAL:
+      case SETGLOBAL:
+        return A;
+      case SETTABLE:
+      case SETTABUP:
+        if(f.isConstant(C)) {
+          throw new IllegalStateException();
+        } else {
+          return C;
+        }
+      case SETTABLE54:
+      case SETI:
+      case SETFIELD:
+      case SETTABUP54:
+        if(code.k(line)) {
+          throw new IllegalStateException();
+        } else {
+          return C;
+        }
+      default:
+        throw new IllegalStateException();
+    }
+  }
+  
+  public int getRegister(int line) {
+    while(code.isUpvalueDeclaration(line) || code.op(line) == Op.EXTRAARG || code.op(line) == Op.EXTRABYTE) {
+      if(line == 1) return -1;
+      line--;
+    }
+    return code.register(line);
+  }
+  
 }
--- a/src/unluac/decompile/Op.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/decompile/Op.java	Wed Sep 20 01:25:14 2023 -0700
@@ -290,7 +290,7 @@
       case VARARG: {
         int a = ex.A.extract(codepoint);
         int b = ex.B.extract(codepoint);
-        if(b == 2) {
+        if(b == 1 || b == 2) {
           return a;
         } else {
           return -1;
@@ -299,7 +299,7 @@
       case VARARG54: {
         int a = ex.A.extract(codepoint);
         int c = ex.C.extract(codepoint);
-        if(c == 2) {
+        if(c == 1 || c == 2) {
           return a;
         } else {
           return -1;
--- a/src/unluac/decompile/block/IfThenEndBlock.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/decompile/block/IfThenEndBlock.java	Wed Sep 20 01:25:14 2023 -0700
@@ -96,7 +96,15 @@
           }
         }
       }
-      if(assign != null && (cond.isRegisterTest() || cond.isOrCondition() || assign.isDeclaration()) && assign.getLastTarget().isLocal() && assign.getLastTarget().getIndex() == test || statements.isEmpty()) {
+      boolean assignMatches = false;
+      if(assign != null) {
+        if(assign.hasExcess()) {
+          assignMatches = assign.getLastRegister() == test;
+        } else {
+          assignMatches = assign.getLastTarget().isLocal() && assign.getLastTarget().getIndex() == test;
+        }
+      }
+      if(assign != null && (cond.isRegisterTest() || cond.isOrCondition() || assign.isDeclaration()) && assignMatches || statements.isEmpty()) {
         FinalSetCondition finalset = new FinalSetCondition(end - 1, test);
         finalset.type = FinalSetCondition.Type.VALUE;
         Condition combined;
--- a/src/unluac/decompile/statement/Assignment.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/decompile/statement/Assignment.java	Wed Sep 20 01:25:14 2023 -0700
@@ -16,10 +16,11 @@
   private final ArrayList<Target> targets = new ArrayList<Target>(5);
   private final ArrayList<Expression> values = new ArrayList<Expression>(5);
   private final ArrayList<Integer> lines = new ArrayList<Integer>(5);
-
+  
   private boolean allnil = true;
   private boolean declare = false;
   private int declareStart = 0;
+  private int register = -1;
   
   public Assignment() {
     
@@ -95,10 +96,34 @@
       value = values.remove(index);
       lines.remove(index);
     }
+    int index = targets.size();
     targets.add(target);
+    values.add(index, value);
+    lines.add(index, line);
+    allnil = allnil && value.isNil();
+  }
+  
+  public boolean hasExcess() {
+    return values.size() > targets.size();
+  }
+  
+  public void addExcessValue(Expression value, int line, int register) {
     values.add(value);
     lines.add(line);
-    allnil = allnil && value.isNil();
+    allnil = false; // Excess can't be implicit
+    int firstRegister = register - (values.size() - 1);
+    if(this.register != -1 && this.register != firstRegister) throw new IllegalStateException();
+    this.register = firstRegister;
+  }
+  
+  public int getRegister(int index) {
+    if(index < 0 || index >= values.size()) throw new IndexOutOfBoundsException();
+    if(register == -1) throw new IllegalStateException();
+    return register + index;
+  }
+  
+  public int getLastRegister() {
+    return getRegister(values.size() - 1);
   }
   
   public Expression getValue(int target) {
--- a/src/unluac/test/LuaSpec.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/test/LuaSpec.java	Wed Sep 20 01:25:14 2023 -0700
@@ -55,6 +55,14 @@
     }
   }
   
+  public boolean compatible(TestFile testfile) {
+    if(testfile.version == TestFile.DEFAULT_VERSION) {
+      return compatible(testfile.name);
+    } else {
+      return this.version >= testfile.version;
+    }
+  }
+  
   public boolean compatible(String filename) {
     int version = 0;
     int underscore = filename.indexOf('_');
--- a/src/unluac/test/TestFiles.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/test/TestFiles.java	Wed Sep 20 01:25:14 2023 -0700
@@ -21,6 +21,9 @@
     new TestFile("multiassign10"),
     new TestFile("multiassign11"),
     new TestFile("multiassign12"),
+    new TestFile("excess01"),
+    new TestFile("excess02", 0x51),
+    new TestFile("excess03"),
     new TestFile("expression"),
     new TestFile("expression02"),
     new TestFile("functioncall"),
@@ -92,6 +95,7 @@
     new TestFile("booleanassign27"),
     new TestFile("booleanassign28"),
     new TestFile("booleanassign29"),
+    new TestFile("booleanassign30"),
     new TestFile("booleanselfassign01"),
     new TestFile("booleanexpression01"),
     new TestFile("booleanexpression02"),
--- a/src/unluac/test/TestSuite.java	Mon Jul 10 22:38:34 2023 -0700
+++ b/src/unluac/test/TestSuite.java	Wed Sep 20 01:25:14 2023 -0700
@@ -85,8 +85,8 @@
       working.mkdir();
     }
     for(TestFile testfile : files) {
-      String name = testfile.name;
-      if(spec.compatible(name)) {
+      if(spec.compatible(testfile)) {
+        String name = testfile.name;
         Configuration config = configure(testfile, base);
         TestResult result = test(spec, uspec, path + name + ext, config);
         report.result(testName(spec, name), result);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/src/booleanassign30.lua	Wed Sep 20 01:25:14 2023 -0700
@@ -0,0 +1,1 @@
+local x = a and b or nil
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/src/excess01.lua	Wed Sep 20 01:25:14 2023 -0700
@@ -0,0 +1,33 @@
+local x, y
+
+x, y = 1, 2, 3
+print(x, y)
+
+x, y = 1, 2, 3, 4
+print(x, y)
+
+x = 1, 2, 3, 4
+print(x)
+
+x = 1, 2
+print(x)
+
+local a = 1, 2
+print(a)
+
+local b, c = 1, 2, 3, 4
+print(b, c)
+
+local u = "upvalue"
+
+x = function() print(u) end, 2
+x()
+
+local d = function() print(u) end, 2
+d()
+
+x = 1, function() print(u) end
+print(x)
+
+local e = 1, function() print(u) end
+print(e)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/src/excess02.lua	Wed Sep 20 01:25:14 2023 -0700
@@ -0,0 +1,28 @@
+local x, y
+
+x = 1, ...
+print(x)
+
+x = 1, ..., 3
+print(x)
+
+x = 1, 2, 3, 4, 5, 6, ... -- try to ensure last stack slot is used only by excess '...'
+print(x)
+
+x, y = 1, 2, ...
+print(x, y)
+
+local a = 1, ...
+print(a)
+
+local b = 1, ..., 3
+print(b)
+
+local c = 1, 2, 3, ...
+print(c)
+
+local d = ..., 2
+print(d)
+
+local d, e = 1, 2, ...
+print(d, e)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/src/excess03.lua	Wed Sep 20 01:25:14 2023 -0700
@@ -0,0 +1,3 @@
+local a = g and h or nil, 2
+local b = 1, g and h or nil
+print(a, b)