diff --git a/CHANGELOG.md b/CHANGELOG.md index e8839e75d..da674dcd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- [#2477] Option to disable AS2 detection of uninitialized class fields +- AS2 detection of uninitialized class fields is cancellable and shows progress + ### Fixed - [#2474] Gotos incorrectly decompiled - AS1/2/3 highligter - brackets parsing error causing memory leak / crash +- Comments color highlighting + + ### Changed +- Icon of "Deobfuscation options" menu from pile of pills to medkit ## [24.0.1] - 2025-06-27 ### Fixed @@ -3877,6 +3885,7 @@ Major version of SWF to XML export changed to 2. [alpha 9]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha8...alpha9 [alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8 [alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7 +[#2477]: https://www.free-decompiler.com/flash/issues/2477 [#2474]: https://www.free-decompiler.com/flash/issues/2474 [#2476]: https://www.free-decompiler.com/flash/issues/2476 [#2404]: https://www.free-decompiler.com/flash/issues/2404 diff --git a/lib/jsyntaxpane-0.9.5.jar b/lib/jsyntaxpane-0.9.5.jar index b8bb16c51..65fc50afb 100644 Binary files a/lib/jsyntaxpane-0.9.5.jar and b/lib/jsyntaxpane-0.9.5.jar differ diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java index a14cc3b12..19b831f94 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/DecompilerPool.java @@ -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> futures = openableToFutures.get(swf); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index ca5c88572..262941bad 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -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> 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> getUninitializedAs2ClassTraits() throws InterruptedException { + public Map> 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(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java index af716de78..090dcca54 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/ActionGraph.java @@ -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; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/UninitializedClassFieldsDetector.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/UninitializedClassFieldsDetector.java index 5dd0f9c8e..a07e29da3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/UninitializedClassFieldsDetector.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/as2/UninitializedClassFieldsDetector.java @@ -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 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 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 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 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); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index c9938f0d4..da1dbfa41 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -1149,6 +1149,10 @@ public final class Configuration { @ConfigurationDefaultBoolean(true) @ConfigurationCategory("script") public static ConfigurationItem showCodeCompletionOnDot = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("script") + public static ConfigurationItem skipDetectionOfUnitializedClassFields = null; private static Map configurationDescriptions = new LinkedHashMap<>(); private static Map configurationTitles = new LinkedHashMap<>(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties index f736fb975..daa5fb3bb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources.properties @@ -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. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties index fa6774568..1fdc7d12b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/locales/AppResources_cs.properties @@ -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. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommentItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommentItem.java index c62c36203..5f7d91d1f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommentItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/CommentItem.java @@ -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; } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/CancellableWorker.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/CancellableWorker.java index bcf1de4cd..231dde36a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/CancellableWorker.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/CancellableWorker.java @@ -66,6 +66,8 @@ public abstract class CancellableWorker implements RunnableFuture { private CancellableWorker parentWorker; private boolean canceled = false; + + private boolean userCancelled = false; private List cancelListeners = new ArrayList<>(); @@ -188,6 +190,15 @@ public abstract class CancellableWorker implements RunnableFuture { 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; diff --git a/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript.flex b/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript.flex index 5d6150586..6dd9637c3 100644 --- a/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript.flex +++ b/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript.flex @@ -64,9 +64,13 @@ import java.util.List; this.offset = ofst; prevToken = null; Token t = yylex(); - prevToken = t; + if (t.type != TokenType.COMMENT) { + prevToken = t; + } for (; t != null; t = yylex()) { - prevToken = t; + if (t.type != TokenType.COMMENT) { + prevToken = t; + } tokens.add(t); } } catch (IOException ex) { @@ -187,9 +191,14 @@ RegExp = \/([^\r\n/]|\\\/)+\/[a-z]* "typeof" | "void" { return token(TokenType.KEYWORD); } + /* comments */ + {Comment} { return token(TokenType.COMMENT); } + {RegExp} { - if (prevToken == null || (prevToken.type == TokenType.OPERATOR && prevToken.pairValue >= 0)) { + if (prevToken == null + || (prevToken.type == TokenType.OPERATOR && prevToken.pairValue >= 0) + ) { return token(TokenType.REGEX); } else { int ch = yychar; @@ -201,7 +210,7 @@ RegExp = \/([^\r\n/]|\\\/)+\/[a-z]* /* operators */ - "(" { return token(TokenType.OPERATOR, PAREN); } + "(" { return token(TokenType.OPERATOR, PAREN); } ")" { return token(TokenType.OPERATOR, -PAREN); } "{" { return token(TokenType.OPERATOR, CURLY); } "}" { return token(TokenType.OPERATOR, -CURLY); } @@ -286,9 +295,7 @@ RegExp = \/([^\r\n/]|\\\/)+\/[a-z]* // JavaDoc comments need a state so that we can highlight the @ controls - /* comments */ - {Comment} { return token(TokenType.COMMENT); } - + /* whitespace */ {WhiteSpace} { } /* identifiers */ diff --git a/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript3.flex b/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript3.flex index 6ef37106c..cf6ae4916 100644 --- a/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript3.flex +++ b/libsrc/jsyntaxpane/jsyntaxpane/src/main/jflex/jsyntaxpane/lexers/actionscript3.flex @@ -66,9 +66,13 @@ import java.util.List; this.offset = ofst; prevToken = null; Token t = yylex(); - prevToken = t; + if (t.type != TokenType.COMMENT) { + prevToken = t; + } for (; t != null; t = yylex()) { - prevToken = t; + if (t.type != TokenType.COMMENT) { + prevToken = t; + } tokens.add(t); } } catch (IOException ex) { @@ -211,6 +215,9 @@ VerbatimString = "@\"" {VerbatimStringCharacter}* "\"" "new" { prevNew = true; return token(TokenType.KEYWORD); } + /* comments */ + {Comment} { return token(TokenType.COMMENT); } + {RegExp} { prevNew = false; if (prevToken == null || (prevToken.type == TokenType.OPERATOR && prevToken.pairValue >= 0)) { @@ -324,12 +331,7 @@ VerbatimString = "@\"" {VerbatimStringCharacter}* "\"" {DoubleLiteral} | {DoubleLiteral}[dD] { prevNew = false; return token(TokenType.NUMBER); } - - // JavaDoc comments need a state so that we can highlight the @ controls - - /* comments */ - {Comment} { prevNew = false; return token(TokenType.COMMENT); } - + /* whitespace */ {WhiteSpace} { } {XMLBeginOneTag} { diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameStatusPanel.java b/src/com/jpexs/decompiler/flash/gui/MainFrameStatusPanel.java index 0aa9dc90b..36fb5ca49 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameStatusPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameStatusPanel.java @@ -81,7 +81,7 @@ public class MainFrameStatusPanel extends JPanel { cancelButton.addActionListener((ActionEvent e) -> { CancellableWorker w = currentWorker; if (w != null) { - w.cancel(true); + w.userCancel(true); } }); statusLeftPanel.add(loadingPanel); diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index af09053d8..40fb528e3 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -3379,6 +3379,12 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se updateClassesList(); reload(true); } + + public void skipDetectionOfUnitializedClassFieldsChanged() { + clearAllScriptCache(); + updateClassesList(); + reload(true); + } public void renameColliding(final Openable openable) { View.checkAccess(); diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index fa296feef..e62a4cc94 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -1255,7 +1255,7 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener { - setSourceCompleted(asm, htext, finalActions); - }); + CancellableWorker that = this; + //if (withUninitializedClassFields) + ProgressListener progressListener = new ProgressListener() { + @Override + public void progress(int p) { + } + + @Override + public void status(String status) { + Main.startWork(AppStrings.translate("work.decompiling") + " " + status + " ...", that); + } + }; + UninitializedClassFieldsDetector det = asm.getSwf().getUninitializedClassFieldsDetector(); + det.addProgressListener(progressListener); + try { + HighlightedText htext = SWF.getCached(asm, innerActions); + ActionList finalActions = innerActions; + View.execInEventDispatch(() -> { + setSourceCompleted(asm, htext, finalActions); + }); + } finally { + det.removeProgressListener(progressListener); + } } else { ActionList finalActions = innerActions; View.execInEventDispatch(() -> { @@ -611,7 +634,7 @@ public class ActionPanel extends JPanel implements SearchListener() { + @Override + public void configurationItemChanged(Boolean newValue) { + mainPanel.skipDetectionOfUnitializedClassFieldsChanged(); + } + }); + + PopupButton deobfuscateOptionsButton = new PopupButton(View.getIcon("medkit16")) { @Override protected JPopupMenu getPopupMenu() { JPopupMenu popupMenu = new JPopupMenu(); @@ -978,9 +1008,13 @@ public class ActionPanel extends JPanel implements SearchListener