Added: #2477 Option to disable AS2 detection of uninitialized class fields

Added: AS2 detection of uninitialized class fields is cancellable and shows progress
Changed: Icon of "Deobfuscation options" menu from pile of pills to medkit
Fixed: Comments color highlighting
This commit is contained in:
Jindra Petřík
2025-07-01 19:45:04 +02:00
parent 8a3cbf9e2d
commit 5ffc71848b
22 changed files with 225 additions and 37 deletions

View File

@@ -243,6 +243,9 @@ public class DecompilerPool {
future.cancel(true);
throw ex;
} catch (ExecutionException ex) {
if (ex.getCause() instanceof InterruptedException) {
throw (InterruptedException) ex.getCause();
}
Logger.getLogger(DecompilerPool.class.getName()).log(Level.SEVERE, null, ex);
} finally {
List<Future<HighlightedText>> futures = openableToFutures.get(swf);

View File

@@ -576,6 +576,15 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
* Uninitialized AS2 class traits. Class name to trait name to trait.
*/
private volatile Map<String, Map<String, com.jpexs.decompiler.flash.action.as2.Trait>> uninitializedAs2ClassTraits = null;
/**
* Detecting uninitilized class fields
*/
@Internal
private boolean detectingUninitializedClassFields = false;
@Internal
private UninitializedClassFieldsDetector uninitializedClassFieldsDetector = new UninitializedClassFieldsDetector();
/**
* ExporterInfo tag.
@@ -664,6 +673,10 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
*/
private final Object charactersLock = new Object();
public UninitializedClassFieldsDetector getUninitializedClassFieldsDetector() {
return uninitializedClassFieldsDetector;
}
/**
* Sets main GFX exporterinfo tag
*
@@ -6208,24 +6221,40 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
* @throws java.lang.InterruptedException On interruption
*/
public void calculateAs2UninitializedClassTraits() throws InterruptedException {
setDetectingUninitialized(true);
uninitializedAs2ClassTraits = new HashMap<>();
UninitializedClassFieldsDetector detector = new UninitializedClassFieldsDetector();
try {
uninitializedAs2ClassTraits = detector.calculateAs2UninitializedClassTraits(this);
} catch (Throwable t) {
uninitializedAs2ClassTraits = getUninitializedClassFieldsDetector().calculateAs2UninitializedClassTraits(this);
} catch (Throwable t) {
uninitializedAs2ClassTraits = null;
throw t;
} finally {
setDetectingUninitialized(false);
}
}
private synchronized void setDetectingUninitialized(boolean val) {
this.detectingUninitializedClassFields = val;
}
private synchronized boolean isDetectingUninitialized() {
return detectingUninitializedClassFields;
}
/**
* Gets uninitialized class traits in AS2.
*
* @return Map of class name to map of trait name to trait
*/
public synchronized Map<String, Map<String, com.jpexs.decompiler.flash.action.as2.Trait>> getUninitializedAs2ClassTraits() throws InterruptedException {
public Map<String, Map<String, com.jpexs.decompiler.flash.action.as2.Trait>> getUninitializedAs2ClassTraits() throws InterruptedException {
if (Configuration.skipDetectionOfUnitializedClassFields.get()) {
return new LinkedHashMap<>();
}
if (CancellableWorker.isInterrupted()) {
throw new InterruptedException();
}
if (isDetectingUninitialized()) {
return new LinkedHashMap<>();
}
if (uninitializedAs2ClassTraits == null) {
calculateAs2UninitializedClassTraits();

View File

@@ -16,6 +16,7 @@
*/
package com.jpexs.decompiler.flash.action;
import com.jpexs.decompiler.flash.AppResources;
import com.jpexs.decompiler.flash.BaseLocalData;
import com.jpexs.decompiler.flash.FinalProcessLocalData;
import com.jpexs.decompiler.flash.SWF;
@@ -48,6 +49,7 @@ import com.jpexs.decompiler.flash.action.swf5.ActionDefineFunction;
import com.jpexs.decompiler.flash.action.swf5.ActionEquals2;
import com.jpexs.decompiler.flash.action.swf6.ActionStrictEquals;
import com.jpexs.decompiler.flash.action.swf7.ActionDefineFunction2;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.ecma.Null;
import com.jpexs.decompiler.flash.ecma.Undefined;
import com.jpexs.decompiler.graph.AbstractGraphTargetVisitor;
@@ -67,6 +69,7 @@ import com.jpexs.decompiler.graph.ThrowState;
import com.jpexs.decompiler.graph.TranslateStack;
import com.jpexs.decompiler.graph.model.BinaryOpItem;
import com.jpexs.decompiler.graph.model.BreakItem;
import com.jpexs.decompiler.graph.model.CommentItem;
import com.jpexs.decompiler.graph.model.GotoItem;
import com.jpexs.decompiler.graph.model.IfItem;
import com.jpexs.decompiler.graph.model.PopItem;
@@ -129,7 +132,7 @@ public class ActionGraph extends Graph {
this.insideDoInitAction = insideDoInitAction;
this.insideFunction = insideFunction;
}
/**
* Get uninitialized class traits
*
@@ -616,6 +619,9 @@ public class ActionGraph extends Graph {
if (insideDoInitAction && !insideFunction) {
ActionScript2ClassDetector detector = new ActionScript2ClassDetector();
detector.checkClass(uninitializedClassTraits, ret, ((ActionGraphSource) code).getVariables(), path);
if (Configuration.skipDetectionOfUnitializedClassFields.get()) {
ret.add(0, new CommentItem(AppResources.translate("decompilationWarning.as2.noUninitializedClassFieldsDetection")));
}
}
ActionLocalData ald = (ActionLocalData) localData;

View File

@@ -35,6 +35,7 @@ import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.graph.AbstractGraphTargetVisitor;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.ProgressListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -48,6 +49,22 @@ import java.util.Map;
*/
public class UninitializedClassFieldsDetector {
private List<ProgressListener> progressListeners = new ArrayList<>();
public void addProgressListener(ProgressListener listener) {
progressListeners.add(listener);
}
public void removeProgressListener(ProgressListener listener) {
progressListeners.remove(listener);
}
private void fireProgress(String status) {
for (ProgressListener listener : progressListeners) {
listener.status(status);
}
}
/**
* Gets path of variable and its getMembers: a.b.c.d => [a,b,c,d].
*
@@ -210,6 +227,7 @@ public class UninitializedClassFieldsDetector {
DoInitActionTag doi = (DoInitActionTag) asm;
String exportName = doi.getSwf().getCharacter(doi.getCharacterId()).getExportName();
if (exportName != null && exportName.startsWith("__Packages.")) {
fireProgress(key);
List<GraphTargetItem> tree = asm.getActionsToTree();
for (GraphTargetItem item : tree) {
if (item instanceof InterfaceActionItem) {
@@ -265,7 +283,7 @@ public class UninitializedClassFieldsDetector {
}
}
}
}
}
classesAsms.add(doi);
}
}
@@ -295,6 +313,7 @@ public class UninitializedClassFieldsDetector {
DoInitActionTag doi = (DoInitActionTag) asm;
String exportName = doi.getSwf().getCharacter(doi.getCharacterId()).getExportName();
if (exportName != null && exportName.startsWith("__Packages.")) {
fireProgress(key);
List<GraphTargetItem> tree = asm.getActionsToTree();
for (GraphTargetItem item : tree) {
if (item instanceof ClassActionItem) {
@@ -347,6 +366,7 @@ public class UninitializedClassFieldsDetector {
throw new InterruptedException();
}
ASMSource asm = asms.get(key);
fireProgress(key);
List<GraphTargetItem> tree = asm.getActionsToTree();
for (GraphTargetItem item : tree) {
AbstractGraphTargetVisitor visitor = new AbstractGraphTargetVisitor() {
@@ -383,6 +403,22 @@ public class UninitializedClassFieldsDetector {
item.visitRecursively(visitor);
}
}
//Removed cached version of classes - allow reparsing using detected uninitialized fields
for (String key : asms.keySet()) {
if (CancellableWorker.isInterrupted()) {
throw new InterruptedException();
}
ASMSource asm = asms.get(key);
if (asm instanceof DoInitActionTag) {
DoInitActionTag doi = (DoInitActionTag) asm;
String exportName = doi.getSwf().getCharacter(doi.getCharacterId()).getExportName();
if (exportName != null && exportName.startsWith("__Packages.")) {
SWF.uncache(doi);
}
}
}
/*for (String cls:result.keySet()) {
System.err.println("class "+cls);

View File

@@ -1149,6 +1149,10 @@ public final class Configuration {
@ConfigurationDefaultBoolean(true)
@ConfigurationCategory("script")
public static ConfigurationItem<Boolean> showCodeCompletionOnDot = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("script")
public static ConfigurationItem<Boolean> skipDetectionOfUnitializedClassFields = null;
private static Map<String, String> configurationDescriptions = new LinkedHashMap<>();
private static Map<String, String> configurationTitles = new LinkedHashMap<>();

View File

@@ -61,3 +61,6 @@ configurationFile.meta.showComments = Show configuration comments - set to true
configurationFile.meta.modifiedOnly = Store modified items only in this file - set to false (and exit app again to resave) to show all.
configurationFile.configuration = Section - Actual configuration
configuration.removed = WARNING: This configuration was REMOVED. It is unused.
#after 24.0.1
decompilationWarning.as2.noUninitializedClassFieldsDetection = WARNING: This class was decompiled without detecting uninitialized class fields.

View File

@@ -62,3 +62,6 @@ configurationFile.meta.showComments = Zobrazit koment\u00e1\u0159e ke konfigurac
configurationFile.meta.modifiedOnly = Ukl\u00e1dat do tohoto souboru pouze zm\u011bn\u011bn\u00e9 hodnoty - nastavte na false (a ukon\u010dete aplikaci pro nov\u00e9 ulo\u017een\u00ed) pro zobrazen\u00ed v\u0161eho.
configurationFile.configuration = Sekce - Vlastn\u00ed konfigurace
configuration.removed = VAROV\u00c1N\u00cd: Tato konfigurace byla ODSTRAN\u011aNA. Nepou\u017e\u00edv\u00e1 se.
#after 24.0.1
decompilationWarning.as2.noUninitializedClassFieldsDetection = VAROV\u00c1N\u00cd: Tato t\u0159\u00edda byla dekompilov\u00e1na bez detekce neinicializovan\u00fdch pol\u00ed t\u0159\u00edd.

View File

@@ -52,7 +52,18 @@ public class CommentItem extends GraphTargetItem {
@Override
public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) {
writer.append("/* ");
int commentLinesCount = 0;
for (int i = 0; i < commentLines.length; i++) {
if (commentLines[i] == null) {
continue;
}
commentLinesCount++;
}
if (commentLinesCount == 1) {
writer.append("// ");
} else {
writer.append("/* ");
}
for (int i = 0; i < commentLines.length; i++) {
if (commentLines[i] == null) {
continue;
@@ -62,7 +73,10 @@ public class CommentItem extends GraphTargetItem {
writer.newLine();
}
}
return writer.append(" */");
if (commentLinesCount > 1) {
writer.append(" */");
}
return writer;
}
/**

View File

@@ -66,6 +66,8 @@ public abstract class CancellableWorker<T> implements RunnableFuture<T> {
private CancellableWorker parentWorker;
private boolean canceled = false;
private boolean userCancelled = false;
private List<Runnable> cancelListeners = new ArrayList<>();
@@ -188,6 +190,15 @@ public abstract class CancellableWorker<T> implements RunnableFuture<T> {
THREAD_POOL.execute(this);
}
public final boolean userCancel(boolean mayIterruptIfRunning) {
userCancelled = true;
return cancel(mayIterruptIfRunning);
}
public boolean isUserCancelled() {
return userCancelled;
}
@Override
public final boolean cancel(boolean mayInterruptIfRunning) {
canceled = true;