diff options
author | Tor Norbye <tnorbye@google.com> | 2013-09-27 10:19:19 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2013-09-27 10:19:28 -0700 |
commit | f7998d05c40c24ae66d1972abfcb070552b1d7b5 (patch) | |
tree | b0b97491f6e6591e53ea6a8f6c3a79a7dc685771 /java/java-analysis-impl/src/com/intellij/codeInspection | |
parent | beca9839b2866f90da9dc517c29df2ec25a6f6a8 (diff) | |
download | idea-f7998d05c40c24ae66d1972abfcb070552b1d7b5.tar.gz |
Snapshot cd724ea5e27634f1c84f893f10b646937a677d56 from idea/132.425 of git://git.jetbrains.org/idea/community.git
cd724ea: Code cleanup - Idea's warnings fixed - message moved to .properties file
c84855b: for performance use processNames api for java symbol contributor
334c509: IDEA-114064 Create "From Maven" library dialog doesn't handle full coords correctly (as advertised)
4e10a17: IDEA-109943 Download Library from Maven Repository: artifacts from repository with provider != maven2 are suggested, but repository is filtered off
d4bc48d: IDEA-114037 Code completion should prefer variable names to unimported class names
b7e5e6c: refix RUBY-11716: do not play with fire, always save and load in UTF-8 !
bb7ed8a: cleanup
15bd24e: cleanup
e56b270: ProjectId, don't add project level provider if project is not mapped
a0fb6a5: let IsNullCheck mean null->true, the former !null->false meaning was useless
26402542: test data fixed
f800733: new inference: input/output inference variables
56d872d: NPE: find usage for db element
3ffd6a0: IDEA-114003 XDebuger breakpoint properties: strage selection jumps in breakpoint tree
bbdef54: IDEA-114001 XDebuger Breakpoint Properties: enable in tree is not saved IDEA-114002 XDebuger breakpoint properties: don't close the dialog by double click IDEA-114004 XDebugger breakpoint properties: checkboxes synchronization is broken
f6ff871: IDEA-114001 XDebuger Breakpoint Properties: enable in tree is not saved IDEA-114002 XDebuger breakpoint properties: don't close the dialog by double click IDEA-114004 XDebugger breakpoint properties: checkboxes synchronization is broken
8f3d962: IDEABKL-6897 Enter inserts closing brace in wrong place
6a78643: IDEABKL-6897 Enter inserts closing brace in wrong place
06b899c: IDEA-114001 XDebuger Breakpoint Properties: enable in tree is not saved IDEA-114002 XDebuger breakpoint properties: don't close the dialog by double click IDEA-114004 XDebugger breakpoint properties: checkboxes synchronization is broken
aeda985: don't give focus to ant messages view
f525708: new inference: overload resolution for SAM return type for implicit lambda should be ignored
6692602: cleanup current file from highlighting markup
27c6645: compilation fix
1936bb2: IDEA-107453 Compilation error if overriding methods have different access modifiers
0ddcc36: api
a7f05e1: card layout fixed
b007608: IDEA-110203 IDEA ignores space after comma setting for methods declaration/call [CR-IC-2022]
ae02c17: hiding artifacts
90e6ed2: back to AddModuleWizard
4a029e9: Groovy: Pull-up members
6f9e810: NPE
fc48bab: pull-up
cbf7531: prepare pull-up refactoring for Groovy
6762d5f: cleanup
b26ffb2: spaces around inner classes
41e8e70: IDEA-113333 Java formatter breaks source code: Merges statements into line comments [CR-IC-2245]
ea91b55: IDEA-113815 keep "simple methods in one line" does not keep long methods [CR-IC-2486]
ecd65ad: Import Eclipse code style settings from XML profile (a part of IDEA-104068) [CR-IC-2219]
aacf1e4: IDEA-113844 (handling of core component initialization failures when spoiled by plugins)
8dc3ede: IDEA-112387 Reformat code with rearrange entires on = fail on enum [CR-IC-2205]
72ddb9e: Merge remote-tracking branch 'origin/master'
209340b: Add the description for MavenDuplicatePluginInspection.
de367e1: Merge branch 'svn1_8_new'
1a70497: IdeaTestAssistant: fix test data
d6a2510: svn: Fixed "Import" when path/url contains '@' symbol
94c4223: svn: Implemented "Export" action for command line
8913712: parameter popup: escape for annotation methods (IDEA-113971)
fdf7312: svn: Made event handler for checkout/export utilize passed progress indicator (instead of just current thread progress indicator)
f653713: IDEA-105758: Contradiction between error and fix actions (Java EE artifact)
eac456f: Platform: ability to provide native icons for PsiElements (PsiFile/Directory) AppCode: blue icons for folders
4d5c47a: IDEA-77519: Project fails to open when workspace.xml is empty
c40e732: two classes temporary restored to fix backward compatibility
b6b3bc6: added optional dependencies for framework support providers
a9dae78: IDEA-90661: recognize IBM JDK jars
6b44958: cleanup & javadoc
0c0a6a6: IDEA-108785 Allow applying the same context to many selected live templates
247661b: don't search for unknown path macros in the middle of xml unless asked so by PathMacroFilter (IDEA-102674)
90222d5: hopefully fix control flow building stack inconsistency assertions in case when a PCE is thrown
241e44b: EA-50288 - CCE: RefJavaUtilImpl.getTopLevelClass
22d5c2f: ensure to filter already inserted annotations (IDEA-113785)
27b76b4: inplace introducer: another case to restore expression (IDEA-113352)
603e138: add mnemonics (IDEA-113889)
a8c374c: XSuspendPolicyPanel "Make default" (requires for new JavaBreakpointType) remove unused methods
4f19472: CR-IC-2485 (deprecation policy specified; deprecated API usage upgraded)
7231807: JavaBreakpointType isSuspendThreadSupported true
8637e89: register JavaBreakpointType, but hide under system property java.debugger.xBreakpoint
0b2d5f0: cleanup
5cc1f5e: EA-50289 (CCE: TooBroadScopeInspection$TooBroadScopeInspectionFix.getCommentText)
505b86e: new inference: pertinent to applicability
8ac21fd: cleanup
2c5f39e: cleanup
f6efaa9: simplify DebuggerInvocationUtil
bf8865f: overrides
0d49e5b: JavaBreakpointType canPutAt
33ebea3: extract XLineBreakpointTypeBase
de7d963: extract XDebuggerEditorsProviderBase, init JavaBreakpointType (is not registered, so, not in action now)
ed777e6: remove deprecated canPutAt
9163435: overrides
44af67f8: extract XDebuggerUtil.getInstance().getGroupingByFileRuleAsList()
b93b27c: add missing Overrides
0857036: cleanup
ae791a7: new inference: test preparations
48120ab: IdeaTestAssistant: add resolving and completion inside TestDataPath annotation
971544b: remove empty unused class
c5642d9: hippie completion: split complex tokens by spaces
e1383b1: refix and add test for IDEA-90294 Don't use substring match in word completion
fbae94c: EA-49809 Made client factories final in SvnVcs
2b4b70f: Fix OC-8127: Appcode hangs on reformatting (endless right shift) +review CR-OC @Anton.Makeev, @Rustam.Vishnyakov
b7314cf: fix pycharm detection
5f0bb3c: revert error checking
5f99590: svn: Implemented "Import" action for command line
ad04905: svn: Changed import logic to use common commit event handler (instead of checkout event handler)
1a45be0: custom options
8d278a3: MavenArchetypesPanel extracted
32eeb4c: aggregation panel invisible
9388cc4: new project wizard: project type sorting
2c02a07: svn: Add "Skipped" event processing in commit/import output for command line
d365580: svn: Refactored commit output parsing for command line
b1cc312: immutable dfa offset stack
5c494fb: toolbarIcon is nullable
4304880: dfa: traverse only flushable variables, not all
ed08eab: immutable EqClass
03de519: overrides
f8661d9: return empty collection if list of storage files is empty
81a998c: IDEA-111030: Add Framework Support: Ok is disabled for the only selected Web Application
4ce1cc8: unused class removed
84f7401: svn: Added final status bar message for Import ("Committed revision xxx")
aa1d479: library editor: 'attach javadoc' extracted to separate button so the main '+' button won't show popup
1612198: cleanup
e180456: cleanup
546fffe: Merge remote-tracking branch 'origin/master'
c3caf6c: Merge remote-tracking branch 'origin/master'
5cd2b37: assertion for EA-45385 - NPE: XmlTagImpl.getDescriptor
99563ea: EA-49418 - NPE: InjectLanguageAction.invokeImpl
dc82859: cleanup
f70ef96: EA-50139 - assert: TextRange.<init>
990e410: svn: Removed unused code (from "Ignore" functionality)
6604613: svn: Implemented "Ignore" functionality on subversion level
d544d03: cleanup after notnullification
5330d9e: plugin suggester: suggest plugins from repository by unknown facet
1ccf92a: error which should never happen replaced by assertion
889d3ce: improved dialog for choosing root types of added roots
0eb34fa: hide "use out of process build" option from UI
d79e13e: plugin suggester: suggest plugins from repository by unknown facet
00a127b: Use shell options only if applicable.
b4f21b9: Don't fail to create SSH terminal session if we failed to create local terminal session.
8e5cb57: test for "Unnecessary unicode escape sequence" inspection
dc8acce: temp revert
c7c4431: fix SliceBackwardTest
3e16a04: svn: Implemented "Edit Revision Comment" action for command line
c54948a: 'async' added
e4f7950: svn: Correctly create externals that have '@' in url - add '@' at url end
9b66e1c: svn: Fix line separators duplication for "Create External" action for SVNKit
a98dc14: svn: Implemented "Create External" action for command line
32e30d1: EA-50206 - assert: FileManagerImpl.findFile
4a4c0cc: svn: Unify line separator for multiline properties
4b9422b: Reverted: Semantic highlighting level to avoid conflicts with "unused symbol" annotations [CR-IC-2435]
ad970ef: lambda: propagate wildcards elimination
e78ab51: lambda: check formal params for equality, eliminate wildcards during inference according to 15.27.3
fc9a196: new inference: ignore proper types in mutual eq constraints generation
7bc0048: new inference: void compatible according to return values
939fc45: prepare for test new inference
eef6eb2: new inference: exact method reference
172daec: new inference: eliminate delayed constrains according to 0.6.3
ec93384: postpone type evaluation
e689d68: new inference: make use of ex constraint
7838f8e: Merge branch 'safe-sudo-escaping'
1c22df9: Add grails-app/resources as resources folder, not a source folder.
a7f094d: fix NPE
e1fe819: Added ExecUtil.sudoAndGetOutput() with safe escaping and quoting for Mac and Linux
6bf3a02: fixed EA-48905 - SIOOBE: ParameterInfoComponent$OneLineComponent.buildLabelText
c853c69: system dependent paths in groovy shell
db7d97f: Merge remote-tracking branch 'origin/master'
65426f9: mark as DumbAware
0d1a074: Support active links in GotIt panel
c7297a0: debugging blinking test
755c6b2: IDEA-113938 "Submit feedback" should pre-fill project and affected version
ff45141: dfa: don't go into the same instruction twice with the same state
3361944: a bit more parsimonous DfaMemoryStateImpl.createCopy
028d28b: dfa states should not change while in queue => no need to copy them
1709382: dfa: use UnorderedPair instead of two-element set
e8dbc21: immutable DfaVariableState, for faster copying and less memory usage
666ed52: IDEA-70241 (Replace with '{@code}' inspection doesn't replace all occurrences in file.)
71bd9cf: Merge remote-tracking branch 'origin/master'
c923098: EA-47881 - IOE: GroovyPsiElementFactoryImpl.createGroovyFileChecked
8b97940: cleanup logging
6e4b81e: EA-50137 - assert: TestObject.addClassesListToJavaParameters
5381d51: cleanup
dc2d1b2: notnull
f419fab: "todo" moved out of lang-impl
97cd633: attributes cleanup
75b3eaa: notnull
c7fa9af: cleanup
f5b5bf4: cleanup
776b16b: cleanup
bf3cea4: made fields final
87913f7: cleanup
798e94e: cleanup, get rid of buggy duplicate node renderer
50f13e4: removed deprecated methods from ExternalAnnotator
6d9c887: avoid deprecated methods
4852116: minor
9cd549c: now Searchable. fixes test.
302302a: typo
26d8885: - handle strings with more than one quote used for start / end delimiters - proper retrieving syntax highlighter in case of non languge based syntaxhighlighter (quite often it is bound to file type) - Find: String literals only: Throwable at StringSearcher.scan() on XML with a string (IDEA-113885) - fix for backward search not ending when whole word option used
4dfd0c7: Encapsulate field dialog: explicit value for "Use accessors when field is accessible" when "as is" visibility is selected
e1aff45: typo :(((
6237b9b: test fixed
6c0d31b: test fixed
493cfae: update test data
711bca1: update test data
f1bc615: update test data
8dd18de: update test data
e61a901: update test data
0be96e9: update test data
aac178b: dfa: remove queued state duplication
6a25f5d: dfa: don't reschedule already processed states, cleanup
7559dfc: Semantic highlighting level to avoid conflicts with "unused symbol" annotations [CR-IC-2435], for WI-19396, WI-20126 (cherry picked from commit 56d66dc)
7c27aef: extract collectUsedJars() function
12f59ff: update test data
03df890: update test data
1efd604: update test data
2c1726e: update test data
24bf1d1: Merge remote-tracking branch 'origin/master'
8f26a8b: improved duplicates search in python extract method
9bfcbbe: setup resource roots when project is imported from Maven (IDEA-57398)
64594cd: diagnostics for EA-49831
0d76c32: add support for frameworks step: sorting restored
b510334: IDEA-113294 indentation of brace in a lambda expression corrected [CR-IC-2426]
9651be4: IDEA-113910 Gradle: code insight; dependencies DSL resolving
25595c8: new inference: checked exceptions compatibility constraint
6b8f295: dump highlighting test data without markup
636719b: now that we have dfa state hashing, use it instead of linear lookup
3813120: dfa: don't merge states when there's only one
3fd9ba8: IDEA-113910 Gradle: code insight; dependencies DSL resolving
57a6aeb: IDEA-113910 Gradle: code insight; dependencies resolving
da23b6c: Merge remote-tracking branch 'origin/master'
7ca6ea3: Merge remote branch 'origin/master'
cdc6d6a: show deprecated make implementation warning once on first compilation after project opening
090c4e3: dfa state merging: cache copies
e6ee024: Allows getting Gradle home without having a Project.
0391639: Gradle VM Options are now saved in between sessions.
f5412f0: resource root: show 'New Directory' action instead of 'New Package' under resource roots
1637f6b: notnullification
87f394f: IDEA-113904 (Add New Module from Project Structure dialog wants to create new project)
a7c75f6: IDEA-113865 ('Equals should check class of parameter' shouldn't warn on identity equals)
2486e9e: dfa: merge several states to account for variables with several possible values
4d4f4b0: dfa: some minor things and caching
c14961f: UnorderedPair in platform
5ad442d: Merge remote-tracking branch 'origin/master'
946a281: Scroll to bottom on typing in terminal (PY-10344).
e5a0548: Close all connections on dispose.
f61f6a2: changes from tech-writers
b82b0bb: dfa: state merging interruptibility
3893373: VcsDirtyScopeManagerImpl: log who marks everything dirty
41e5381: Merge remote-tracking branch 'origin/master'
da21efb: JediTerm updated.
beb1d1a: Merge remote-tracking branch 'origin/master'
ed3a2b5: skip the whole document if some component has disabled roaming type (details CR-IU-308)
d66309b: Fix antialiasing.
e55fae1: dfa: abstract out eq class into EqClass class
370d44d: dfa: remove trivial state facts that constant != another constant
4ccfcf7: UsagesStatistic must specify roaming disabled
75894ae: cleanup
784b4e3: DimensionService: cleanup, order of stored data should be stable
36443b6: overrides
b70b1dc: overrides
dfe38fb: simplify some constant conditions and greenify
9a08478: IDEA-85961 (Pattern BACKSLASH_PATTERN = Pattern.compile("\\", Pattern.LITERAL) is always marked red.)
d0bdc3b: simplify load from providers – we don't need to filter again (our save do it, in any case it is absurd to store component with global roaming in the project level file, — should be refine later)
bf85e1b: cleanup
9c30823: remove unused methods
f671c09: overrides
3bd62bc: ComponentRoamingManager should not keep defaults (we use RoamingType.PER_USER by default)
a8f1063: CR-IU-300 remove outdated EP ComponentRoamingType
b182751: CR-IU-300 remove outdated RoamingTypePerPlatform
994389c: another java.util.regex.Pattern.compile() parameter annotation
301710d: IDEA-113866 (this. not suggested for fields of anonymous inner classes)
f5d03c4: dfa: don't consider final getters same as immutable fields
272a6d0: Corresponding parents for console colors.
71298ce: Bright console colors for Monokai.
f39c34b: Console colors for WarmNeon scheme.
7548e47: Black is invisible on dark background (in RegExps for example).
ca6b784: Change bloody red to light pink for numbers (pink is specific to Neon color schemes while red is not, also eyes say thanks).
7f627d9: Console colors for Twilight scheme fixed.
a5eb2fc: Console colors for Monokai scheme fixed.
f7da145: DfaPsiType: add @NotNull
b216102: rebomb test
b349547: diagnostics for inconsistencies during control flow building
6823425: dfa: only perform costly state merging when it has chances - after jumps
fea8871: dfa: fighting too complex methods; join complementary memory states after fork to avoid having too many states
0ac2084: DfaMemoryStateImpl: introduce unwrap; compare variable values with their non-initialized counterparts
e919b8b: dfa: only goto catch on non-trivial method calls and throws
e2ea04f: new inference: initial method reference constraint
a9dde36: new inference: check substituted descriptor return type
3e5b164: new inference: expression inside condition should be poly, target type for conditional expression
ff9f2e9: new inference: emulate fresh variable - do not override vars with captured ones
f415702: new inference: default constructor as poly expression argument
ee56497: new inference: symmetric variable bounds
4a46b24: new inference: init inter call inference
06829c5: Merge remote-tracking branch 'origin/master'
e3f213f: Console colors for Darcula.
f748505: IDEA-57940 Cyclic expand word should take into account all open files
460ef47: StreamProvider.isVersioningRequired
8029f47: Bright yellow made more visible on white background.
96006be: Default console colors as in xterm palette, gray and dark gray from standard vga palette for better readability.
a378414: Bight console colors added to settings.
54e6582: builder-based project types
b0b2cf8: template-based project types?
e741484: CPU hogging fix again
eb22a99: fix todo duplication when several pattern match -> prefer finding match with last pattern (in settings list), thus default TODO pattern is matched last
a4d4371: fix compilation
2f823ba: Difference Groovy Shell & Groovy Console actions
3e876f4: IDEA-113590 annotations as annotation values
732cafd: dead code
a712f87: pull up 'isQualified()' method
4edd102: extract base class from PullUpDialog
10d89d6: remove obsolete test
a0750e4: delete envFile manually
114cbb2: IDEA-113861 Gradle: it could be possible to hide 'Gradle: download' progress to the background
ca416b8: 'More' element for classes, files, action, and settings. Better renderer.
b2a550c: test framework: drop temp directory on light project close
8aaf53b: greenify ActionsTreeUtil a bit
922d41a: layout
2cd7326: adding frameworks support
2bd8fb0: cleanup
4ea6442: ProjectSummaryStep
9fdcdcd: commit project name
bd39918: new project wizard: first test
630fc14: External system: use URLs in compile output paths
85dbe5f: IDEA-65114 "Add Maven projects" cannot be undone
5041ae5: test framework: returning of the data provider
16e2072: @Nullable XBreakpoint.getProperties reverted +review CR-IC-2418
df4da38: dump shell environment to a temporary file to reduce probability of malformed lines occurrence
280d52a: let event log warning color be orange (IDEA-113802)
a30b2ae: IDEA-113836 Console folding: add TestNG related patterns
cbeb0f1: don't change mouse cursor during goto name population (IDEA-113800)
fb5ad5b: IDEA-113638 ChooseByName restart on write action spawns a new thread
89b3767: disable autopopup in groovy shell if selection by chars is enabled (IDEA-112820)
9fa36be: CompletionConfidence: don't force API users to implement unused method
34cf7eb: Console Folding: proper capitalization
5e8aa44: IDEA-113855 Search Everywhere looks scrambled at first start
71723cb: svn: Refactored executable validation - use separate version client
1114cfe: javadoc
0738800: jps model: simplification, source roots always have default properties
b1d5062: jps model: JpsElementType converted to interface to allow reusing common base class
229deb0: test framework: ok, put light project file into ephemeral directory, but keep it for a project's life
6e8b950: ensure "thread" suspend policy for logging breakpoints
d67c04e: EA-49809 Move client factories creation to SvnVcs constructor (instead of active() method)
935cdba: new inference: initial tests
970a180: encapsulate read access to USE_COMPILE_SERVER option
b9b3fc8: remote agents - extract to remote servers
cfe8e2b: WEB-9335 Bad insert pair brace in CSS
2815c22: WEB-9334 Incremental selection works bad with negative CSS values
d023471: new "Unnecessary unicode escape sequence" inspection
945b069: unicode escape needs at least one 'u'
8e7797b: chrome still crashed, revert to old, not-recursive speed search
1917d86: Merge remote-tracking branch 'origin/master'
c18e6bd: Lense mode "internal" preview
db88427: Merge branch 'svn1_8_new'
7c64e8b: test framework: do not put light project file into ephemeral directory
af09e11: remove @Nullable from key.get as it's too generic
d49df32: svn: Refactored prompting for working copy format - make return not null format
d6086ae: dfa: types with wildcard parameters are not equal
3d28aa5: Optimization: check exiting of griffon-app first
b016c04: correct ephemeral state copy (IDEA-113143 Calling method with contract shouldn't result in nullability suspicion)
189573f: svn: Refactored upgrade working copy format dialog - use list of available formats (instead of separate fields for each format)
60c471b: Rename test
1a42f59: Remove using of unnecessary StringBuilder
a56db90: Use MultiMap
340cf22: Calling method with contract shouldn't result in nullability suspicion (IDEA-113763, IDEA-113699, almost IDEA-113143)
f2a563b: dfa: unify nullability violation processing
709af86: dfa: expand contract test
1cddfb4: dfa cleanup: skipping reports on method calls is now done via unknown variable mechanism
2765dab: dfa: spare some minor cpu cycles
8bc7465: svn: Added "1.8" option to upgrade/checkout dialogs
c266bd2: IDEA-66603 Maven3: provide inspection that checks duplicate declarations of plugins
af32923: SpellChecker: "cyclomatic"
5f4f98f: svn: Refactored working copy format selection dialog to use WorkingCopyFormat instances instead of just strings
e33b58b: MismatchedCollectionQueryUpdateInspection -- added "compute" prefix (from j.u.Map in JDK8)
7d54b2e: new inference: accept nonProper eq bounds
457d95b: new inference: distinguish different captures
5b5f52e: new inference: assertions caused by raw types
3848de4: new inference: open top level captured wildcards
35d59c0: new inference: extend usage of already inferred variables
4f7b572: new inference: inference of calls in arguments during outer call inference
bfa7879: new inference: captured variables from outer calls to be included
ed95269: new inference: eq bound for S<=T constraint reduction
12a0faf: better positioning
13029b7: different position layout algorithms
39e6e21: EA-49923 - Fixed working copy format detection for default project
d521e9d: JDK combobox should show JDK home
17fe05e: Fix typo
7ab769b: IDEA-16077 Maven embedder runs in the 'wrong' JDK add "embedder JDK" option
418ddc0: Inline string constant
6a8f1e2: IDEA-16077 Maven embedder runs in the 'wrong' JDK extract MavenJdkCombobox
46078c4: svn: Refactored detection if command line implementation should be used (use utility method)
24106fc: XDebugger: @Nullable XBreakpoint.getProperties()
3dc6f07: extract util method
476dac4: Reinit checkbox state on create
39cf1fd: Structure viewer for simple editors support fixed
a38a251: Show filters only for table editor + a few model and UI fixes
cbd5630: Initial dynamic filters model + columns header improved
281c008: svn: Support nullable SVNStatusClient in status implementation for SVNKit
1840d42: simplification
fbcba0f: svn: Make command line clients (info, status) use vcs instead of project instances
ae8b41b: Merge remote-tracking branch 'origin/master'
a97397c: Extract method.
3b9eca4: new project wizard: preparing test infrastructure
327ddb0: Lense mode "internal" preview
0ede5e1: new project wizard: AbstractProjectWizard extracted
a94b556: IDEA-111335 Gradle: task tree is incorrectly displayed if tasks are added to sub-projects via 'subprojects' method
bfd366f: Merge remote-tracking branch 'origin/master'
0ef2a82: calls to obsolete method removed from build scripts
2df016f: AntLoggerFactory inner class made static to fix NoSuchMethodException in Logger.setFactory
fe876e2: svn: Clients for update command renamed and moved to corresponding package
a38d90e: Terminal color settings.
bd285b8: svn: Refactored update logic to common ClientFactory model
9cc13da: platform: ignore hidden Windows files
d87664c: test framework: relic property dropped
5f6ef06: svn: Removed unused "common ancestor" behavior from command line update client
3667fe1: Find in strings with reg exp with start / end match markers doesn't work without string delimiters (IDEA-113788)
94a079f: svn: Refactored "update" command - explicitly create new SVNKit update client for each update/switch operation
0344e91: new project wizard: added option to use framework libraries from an app server
2882ac0: svn: Removed unused methods from "update" client
17ea275: svn: Removed unused SvnProxies class
c926181: WEB-9342 External Tool fails on OSX if an executable file basename specified
a3433f9: svn: Removed unused methods from "info" client
ec3112d: Optimize SassExtensionFunctionsIndex
9f1abf7: nosplash shouldn't prevent from plugin update
4c7154d: dfa: use cached nullability
b81e9c3: dfa: less frequent "too long" check
0b0eba9: DfaValueFactory: use List instead of TIntObjectHashMap for sequential keys
95d5433: introduce DfaPsiType without nullability, to quickly check assignability/convertibility in DfaVariableState
b901d90: IDEA-96713 Incorrect options shown for 'implements'
e02e2c4: IDEA-113780 "Annotate" from history fixed for renamed/moved files
fe00f73: test framework: stability improvements
76d664d: platform: suspicious event logging
b204bdf: Cleanup (de-duplication)
7ba9ff0: spelling
ece939c: make public for Upsource
496db4d: xdebugger: rebuild standalone variables view on EDT
eae27cb: xdebugger: supported rebuilding of standalone variables view
8bf5204: xdebugger api: added convenient method
43dbb6f: Terminal options.
3db1f5c: Blink period and antialiasing settings.
88264bd: IDEA-108147 Use "merge sources" wrapper object instead of just string representation as data model for "Merge Sources" column (to correctly get file revision object and show details panel)
111e6ba: IDEA-108147 While building history for element make check "if element parent or child was changed in given revision" only be performed for non-"merge source" revisions
e07828d: svn: Implemented support for "merged revisions" parsing in history logic for command line
1586c4a: svn: history logic refactored for command line - get and parse history data in xml format
0ac1dd2: IDEA-94942 Refactored "Annotate" implementation for command line - use utility method for parsing
ba1e5e0: IDEA-94942 Implemented merge history support for "Annotate" action
4c3b0a8: IDEA-94942 Implemented "Switch" logic (during update)
Change-Id: I7092ae66ff47d353a5b9770d1d91f77369bb7734
Diffstat (limited to 'java/java-analysis-impl/src/com/intellij/codeInspection')
27 files changed, 1199 insertions, 771 deletions
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index 58bb1fc2cc1d..fed5b5b2e54a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -22,7 +22,6 @@ import com.intellij.codeInsight.ExceptionUtil; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; @@ -100,24 +99,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { return JavaPsiFacade.getElementFactory(manager.getProject()).createTypeByFQClassName(fqn, scope); } - private boolean myRecursionStopper = false; - private <T extends Instruction> T addInstruction(T i) { - ProgressManager.checkCanceled(); - - if (!myRecursionStopper) { - myRecursionStopper = true; - try { - // add extra conditional goto in order to handle possible runtime exceptions that could be caught by finally block - if (i instanceof BranchingInstruction || i instanceof AssignInstruction || i instanceof MethodCallInstruction) { - addConditionalRuntimeThrow(); - } - } - finally { - myRecursionStopper = false; - } - } - myCurrentFlow.addInstruction(i); return i; } @@ -137,7 +119,10 @@ class ControlFlowAnalyzer extends JavaElementVisitor { private void finishElement(PsiElement element) { myCurrentFlow.finishElement(element); - assert element == myElementStack.pop(); + PsiElement popped = myElementStack.pop(); + if (element != popped) { + throw new AssertionError("Expected " + element + ", popped " + popped); + } } @Override @@ -146,64 +131,60 @@ class ControlFlowAnalyzer extends JavaElementVisitor { } @Override public void visitAssignmentExpression(PsiAssignmentExpression expression) { + PsiExpression lExpr = expression.getLExpression(); + PsiExpression rExpr = expression.getRExpression(); + startElement(expression); + if (rExpr == null) { + pushUnknown(); + finishElement(expression); + return; + } - try { - PsiExpression lExpr = expression.getLExpression(); - PsiExpression rExpr = expression.getRExpression(); + lExpr.accept(this); - if (rExpr == null) { - pushUnknown(); - return; + IElementType op = expression.getOperationTokenType(); + PsiType type = expression.getType(); + boolean isBoolean = PsiType.BOOLEAN.equals(type); + if (op == JavaTokenType.EQ) { + rExpr.accept(this); + generateBoxingUnboxingInstructionFor(rExpr, type); + } + else if (op == JavaTokenType.ANDEQ) { + if (isBoolean) { + generateNonLazyExpression(true, lExpr, rExpr, type); } - - lExpr.accept(this); - - IElementType op = expression.getOperationTokenType(); - PsiType type = expression.getType(); - boolean isBoolean = PsiType.BOOLEAN.equals(type); - if (op == JavaTokenType.EQ) { - rExpr.accept(this); - generateBoxingUnboxingInstructionFor(rExpr, type); - } - else if (op == JavaTokenType.ANDEQ) { - if (isBoolean) { - generateNonLazyExpression(true, lExpr, rExpr, type); - } - else { - generateDefaultBinOp(lExpr, rExpr, type); - } + else { + generateDefaultBinOp(lExpr, rExpr, type); } - else if (op == JavaTokenType.OREQ) { - if (isBoolean) { - generateNonLazyExpression(false, lExpr, rExpr, type); - } - else { - generateDefaultBinOp(lExpr, rExpr, type); - } + } + else if (op == JavaTokenType.OREQ) { + if (isBoolean) { + generateNonLazyExpression(false, lExpr, rExpr, type); } - else if (op == JavaTokenType.XOREQ) { - if (isBoolean) { - generateXorExpression(expression, new PsiExpression[]{lExpr, rExpr}, type); - } - else { - generateDefaultBinOp(lExpr, rExpr, type); - } + else { + generateDefaultBinOp(lExpr, rExpr, type); } - else if (op == JavaTokenType.PLUSEQ && type != null && type.equalsToText(JAVA_LANG_STRING)) { - lExpr.accept(this); - rExpr.accept(this); - addInstruction(new BinopInstruction(JavaTokenType.PLUS, null, lExpr.getProject())); + } + else if (op == JavaTokenType.XOREQ) { + if (isBoolean) { + generateXorExpression(expression, new PsiExpression[]{lExpr, rExpr}, type); } else { generateDefaultBinOp(lExpr, rExpr, type); } - - addInstruction(new AssignInstruction(rExpr)); } - finally { - finishElement(expression); + else if (op == JavaTokenType.PLUSEQ && type != null && type.equalsToText(JAVA_LANG_STRING)) { + lExpr.accept(this); + rExpr.accept(this); + addInstruction(new BinopInstruction(JavaTokenType.PLUS, null, lExpr.getProject())); } + else { + generateDefaultBinOp(lExpr, rExpr, type); + } + + addInstruction(new AssignInstruction(rExpr)); + finishElement(expression); } private void generateDefaultBinOp(PsiExpression lExpr, PsiExpression rExpr, final PsiType exprType) { @@ -658,11 +639,13 @@ class ControlFlowAnalyzer extends JavaElementVisitor { if (exception != null) { exception.accept(this); + addConditionalRuntimeThrow(); addInstruction(new DupInstruction()); addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, statement.getProject())); ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, true, null); addInstruction(gotoInstruction); + addInstruction(new PushInstruction(myFactory.createTypeValue(myNpe, Nullness.NOT_NULL), null)); addThrowCode(myNpe); gotoInstruction.setOffset(myCurrentFlow.getInstructionCount()); addThrowCode(exception.getType()); @@ -1008,42 +991,40 @@ class ControlFlowAnalyzer extends JavaElementVisitor { public void visitPolyadicExpression(PsiPolyadicExpression expression) { startElement(expression); - try { - DfaValue dfaValue = myFactory.createValue(expression); - if (dfaValue != null) { - addInstruction(new PushInstruction(dfaValue, expression)); - return; - } - IElementType op = expression.getOperationTokenType(); - - PsiExpression[] operands = expression.getOperands(); - if (operands.length <= 1) { - pushUnknown(); - return; - } - PsiType type = expression.getType(); - if (op == JavaTokenType.ANDAND) { - generateAndExpression(operands, type, true); - } - else if (op == JavaTokenType.OROR) { - generateOrExpression(operands, type, true); - } - else if (op == JavaTokenType.XOR && PsiType.BOOLEAN.equals(type)) { - generateXorExpression(expression, operands, type); - } - else if (op == JavaTokenType.AND && PsiType.BOOLEAN.equals(type)) { - generateAndExpression(operands, type, false); - } - else if (op == JavaTokenType.OR && PsiType.BOOLEAN.equals(type)) { - generateOrExpression(operands, type, false); - } - else { - generateOther(expression, op, operands, type); - } + DfaValue dfaValue = myFactory.createValue(expression); + if (dfaValue != null) { + addInstruction(new PushInstruction(dfaValue, expression)); + finishElement(expression); + return; } - finally { + IElementType op = expression.getOperationTokenType(); + + PsiExpression[] operands = expression.getOperands(); + if (operands.length <= 1) { + pushUnknown(); finishElement(expression); + return; } + PsiType type = expression.getType(); + if (op == JavaTokenType.ANDAND) { + generateAndExpression(operands, type, true); + } + else if (op == JavaTokenType.OROR) { + generateOrExpression(operands, type, true); + } + else if (op == JavaTokenType.XOR && PsiType.BOOLEAN.equals(type)) { + generateXorExpression(expression, operands, type); + } + else if (op == JavaTokenType.AND && PsiType.BOOLEAN.equals(type)) { + generateAndExpression(operands, type, false); + } + else if (op == JavaTokenType.OR && PsiType.BOOLEAN.equals(type)) { + generateOrExpression(operands, type, false); + } + else { + generateOther(expression, op, operands, type); + } + finishElement(expression); } private void generateOther(PsiPolyadicExpression expression, IElementType op, PsiExpression[] operands, PsiType type) { @@ -1106,6 +1087,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.UNBOXING, expectedType)); } else if (TypeConversionUtil.isAssignableFromPrimitiveWrapper(expectedType) && TypeConversionUtil.isPrimitiveAndNotNull(exprType)) { + addConditionalRuntimeThrow(); addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.BOXING, expectedType)); } else if (exprType != expectedType && @@ -1275,7 +1257,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { if (type instanceof PsiClassType) { type = ((PsiClassType)type).rawType(); } - addInstruction(new PushInstruction(myFactory.getTypeFactory().createTypeValue(type), null)); + addInstruction(new PushInstruction(myFactory.createTypeValue(type, Nullness.UNKNOWN), null)); addInstruction(new InstanceofInstruction(expression, expression.getProject(), operand, type)); } else { @@ -1301,58 +1283,56 @@ class ControlFlowAnalyzer extends JavaElementVisitor { } @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { - try { - startElement(expression); + startElement(expression); - if (handleContracts(expression, getCallContracts(expression))) { - return; - } + if (handleContracts(expression, getCallContracts(expression))) { + finishElement(expression); + return; + } - PsiReferenceExpression methodExpression = expression.getMethodExpression(); - PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); + PsiReferenceExpression methodExpression = expression.getMethodExpression(); + PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); - if (qualifierExpression != null) { - qualifierExpression.accept(this); - } - else { - pushUnknown(); - } + if (qualifierExpression != null) { + qualifierExpression.accept(this); + } + else { + pushUnknown(); + } - PsiExpression[] expressions = expression.getArgumentList().getExpressions(); - PsiElement method = methodExpression.resolve(); - PsiParameter[] parameters = method instanceof PsiMethod ? ((PsiMethod)method).getParameterList().getParameters() : null; - for (int i = 0; i < expressions.length; i++) { - PsiExpression paramExpr = expressions[i]; - paramExpr.accept(this); - if (parameters != null && i < parameters.length) { - generateBoxingUnboxingInstructionFor(paramExpr, parameters[i].getType()); - } + PsiExpression[] expressions = expression.getArgumentList().getExpressions(); + PsiElement method = methodExpression.resolve(); + PsiParameter[] parameters = method instanceof PsiMethod ? ((PsiMethod)method).getParameterList().getParameters() : null; + for (int i = 0; i < expressions.length; i++) { + PsiExpression paramExpr = expressions[i]; + paramExpr.accept(this); + if (parameters != null && i < parameters.length) { + generateBoxingUnboxingInstructionFor(paramExpr, parameters[i].getType()); } + } - addInstruction(new MethodCallInstruction(expression, createChainedVariableValue(expression))); + addConditionalRuntimeThrow(); + addInstruction(new MethodCallInstruction(expression, createChainedVariableValue(expression))); - if (!myCatchStack.isEmpty()) { - addMethodThrows(expression.resolveMethod()); - } + if (!myCatchStack.isEmpty()) { + addMethodThrows(expression.resolveMethod()); + } - if (expressions.length == 1 && method instanceof PsiMethod && - "equals".equals(((PsiMethod)method).getName()) && parameters.length == 1 && - parameters[0].getType().equalsToText(JAVA_LANG_OBJECT) && - PsiType.BOOLEAN.equals(((PsiMethod)method).getReturnType())) { - addInstruction(new PushInstruction(myFactory.getConstFactory().getFalse(), null)); - addInstruction(new SwapInstruction()); - addInstruction(new ConditionalGotoInstruction(getEndOffset(expression), true, null)); + if (expressions.length == 1 && method instanceof PsiMethod && + "equals".equals(((PsiMethod)method).getName()) && parameters.length == 1 && + parameters[0].getType().equalsToText(JAVA_LANG_OBJECT) && + PsiType.BOOLEAN.equals(((PsiMethod)method).getReturnType())) { + addInstruction(new PushInstruction(myFactory.getConstFactory().getFalse(), null)); + addInstruction(new SwapInstruction()); + addInstruction(new ConditionalGotoInstruction(getEndOffset(expression), true, null)); - addInstruction(new PopInstruction()); - addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); + addInstruction(new PopInstruction()); + addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); - expressions[0].accept(this); - addInstruction(new ApplyNotNullInstruction(expression)); - } - } - finally { - finishElement(expression); + expressions[0].accept(this); + addInstruction(new ApplyNotNullInstruction(expression)); } + finishElement(expression); } private boolean handleContracts(PsiMethodCallExpression expression, List<MethodContract> _contracts) { @@ -1527,9 +1507,9 @@ class ControlFlowAnalyzer extends JavaElementVisitor { if (type == ASSERT_IS_NULL_METHOD || type == ASSERT_IS_NOT_NULL_METHOD) { constraints[checkedParam] = type == ASSERT_IS_NOT_NULL_METHOD ? ValueConstraint.NULL_VALUE : ValueConstraint.NOT_NULL_VALUE; return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); - } else if (type == IS_NULL_METHOD || type == IS_NOT_NULL_METHOD) { - constraints[checkedParam] = type == IS_NULL_METHOD ? ValueConstraint.NOT_NULL_VALUE : ValueConstraint.NULL_VALUE; - return Collections.singletonList(new MethodContract(constraints, ValueConstraint.FALSE_VALUE)); + } else if (type == IS_NOT_NULL_METHOD || type == IS_NULL_METHOD) { + constraints[checkedParam] = ValueConstraint.NULL_VALUE; + return Collections.singletonList(new MethodContract(constraints, type == IS_NULL_METHOD ? ValueConstraint.TRUE_VALUE : ValueConstraint.FALSE_VALUE)); } else { //assertTrue or assertFalse constraints[checkedParam] = type == ASSERT_FALSE_METHOD ? ValueConstraint.TRUE_VALUE : ValueConstraint.FALSE_VALUE; return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); @@ -1594,7 +1574,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { final DfaValue dfaValue; if (type instanceof PsiClassType) { - dfaValue = myFactory.getTypeFactory().createTypeValue(type); + dfaValue = myFactory.createTypeValue(type, Nullness.UNKNOWN); } else { dfaValue = null; @@ -1623,6 +1603,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(new PopInstruction()); } } + addConditionalRuntimeThrow(); addInstruction(new MethodCallInstruction(expression, null)); } else { @@ -1640,6 +1621,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor { } } + addConditionalRuntimeThrow(); addInstruction(new MethodCallInstruction(expression, null)); if (!myCatchStack.isEmpty()) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index bbac522a4c5a..b462090126a2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -33,8 +33,10 @@ import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix; import com.intellij.codeInspection.*; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaConstValue; +import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; @@ -47,6 +49,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtilRt; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jdom.Element; import org.jetbrains.annotations.NonNls; @@ -145,7 +148,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { PsiClass containingClass = PsiTreeUtil.getParentOfType(scope, PsiClass.class); if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass)) return; - final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner(SUGGEST_NULLABLE_ANNOTATIONS) { + final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner() { @Override protected boolean shouldCheckTimeLimit() { if (!onTheFly) return false; @@ -159,7 +162,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { ProblemsHolder holder, StandardDataFlowRunner dfaRunner, Collection<DfaMemoryState> initialStates) { - final StandardInstructionVisitor visitor = new DataFlowInstructionVisitor(dfaRunner); + final DataFlowInstructionVisitor visitor = new DataFlowInstructionVisitor(dfaRunner); final RunnerResult rc = dfaRunner.analyzeMethod(scope, visitor, IGNORE_ASSERT_STATEMENTS, initialStates); if (rc == RunnerResult.OK) { createDescription(dfaRunner, holder, visitor); @@ -213,7 +216,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { protected void addSurroundWithIfFix(PsiExpression qualifier, List<LocalQuickFix> fixes) { } - private void createDescription(StandardDataFlowRunner runner, ProblemsHolder holder, StandardInstructionVisitor visitor) { + private void createDescription(StandardDataFlowRunner runner, ProblemsHolder holder, DataFlowInstructionVisitor visitor) { Pair<Set<Instruction>, Set<Instruction>> constConditions = runner.getConstConditionalExpressions(); Set<Instruction> trueSet = constConditions.getFirst(); Set<Instruction> falseSet = constConditions.getSecond(); @@ -221,28 +224,25 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { ArrayList<Instruction> allProblems = new ArrayList<Instruction>(); allProblems.addAll(trueSet); allProblems.addAll(falseSet); - allProblems.addAll(runner.getNPEInstructions()); allProblems.addAll(runner.getCCEInstructions()); allProblems.addAll(StandardDataFlowRunner.getRedundantInstanceofs(runner, visitor)); - Collections.sort(allProblems, new Comparator<Instruction>() { - @Override - public int compare(Instruction i1, Instruction i2) { - return i1.getIndex() - i2.getIndex(); - } - }); - HashSet<PsiElement> reportedAnchors = new HashSet<PsiElement>(); - - for (Instruction instruction : allProblems) { - if (instruction instanceof MethodCallInstruction) { - reportCallMayProduceNpe(holder, (MethodCallInstruction)instruction, reportedAnchors); + for (PsiElement element : visitor.getProblems(NullabilityProblem.callNPE)) { + if (reportedAnchors.add(element)) { + reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element); } - else if (instruction instanceof FieldReferenceInstruction && - reportedAnchors.add(((FieldReferenceInstruction)instruction).getElementToAssert())) { - reportFieldAccessMayProduceNpe(holder, (FieldReferenceInstruction)instruction); + } + for (PsiElement element : visitor.getProblems(NullabilityProblem.fieldAccessNPE)) { + if (reportedAnchors.add(element)) { + PsiElement parent = element.getParent(); + PsiElement fieldAccess = parent instanceof PsiArrayAccessExpression || parent instanceof PsiReferenceExpression ? parent : element; + reportFieldAccessMayProduceNpe(holder, element, (PsiExpression)fieldAccess); } - else if (instruction instanceof TypeCastInstruction && + } + + for (Instruction instruction : allProblems) { + if (instruction instanceof TypeCastInstruction && reportedAnchors.add(((TypeCastInstruction)instruction).getCastExpression().getCastType())) { reportCastMayFail(holder, (TypeCastInstruction)instruction); } @@ -251,11 +251,15 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } - reportNullableArguments(runner, holder, reportedAnchors); - reportNullableAssignments(runner, holder, reportedAnchors); - reportUnboxedNullables(runner, holder, reportedAnchors); - reportNullableReturns(runner, holder, reportedAnchors); - reportNullableArgumentsPassedToNonAnnotated(runner, holder, reportedAnchors); + reportNullableArguments(visitor, holder, reportedAnchors); + reportNullableAssignments(visitor, holder, reportedAnchors); + reportUnboxedNullables(visitor, holder, reportedAnchors); + if (!runner.isInNullableMethod() && runner.isInMethod() && (runner.isInNotNullMethod() || SUGGEST_NULLABLE_ANNOTATIONS)) { + reportNullableReturns(runner, visitor, holder, reportedAnchors); + } + if (SUGGEST_NULLABLE_ANNOTATIONS) { + reportNullableArgumentsPassedToNonAnnotated(visitor, holder, reportedAnchors); + } if (REPORT_CONSTANT_REFERENCE_VALUES) { reportConstantReferenceValues(holder, visitor, reportedAnchors); @@ -317,15 +321,14 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { return value instanceof String ? "\"" + StringUtil.escapeStringCharacters((String)value) + "\"" : String.valueOf(value); } - private void reportNullableArgumentsPassedToNonAnnotated(StandardDataFlowRunner runner, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { - Set<PsiExpression> exprs = runner.getNullableArgumentsPassedToNonAnnotatedParam(); - for (PsiExpression expr : exprs) { + private void reportNullableArgumentsPassedToNonAnnotated(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { + for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter)) { if (reportedAnchors.contains(expr)) continue; final String text = isNullLiteralExpression(expr) ? "Passing <code>null</code> argument to non annotated parameter" : "Argument <code>#ref</code> #loc might be null but passed to non annotated parameter"; - LocalQuickFix[] fixes = createNPEFixes(expr, expr); + LocalQuickFix[] fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr); final PsiElement parent = expr.getParent(); if (parent instanceof PsiExpressionList) { final int idx = ArrayUtilRt.find(((PsiExpressionList)parent).getExpressions(), expr); @@ -349,22 +352,15 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } - private void reportCallMayProduceNpe(ProblemsHolder holder, MethodCallInstruction mcInstruction, Set<PsiElement> reportedAnchors) { - if (mcInstruction.getCallExpression() instanceof PsiMethodCallExpression) { - PsiMethodCallExpression callExpression = (PsiMethodCallExpression)mcInstruction.getCallExpression(); - if (!reportedAnchors.add(callExpression)) return; - - LocalQuickFix[] fix = createNPEFixes(callExpression.getMethodExpression().getQualifierExpression(), callExpression); + private void reportCallMayProduceNpe(ProblemsHolder holder, PsiMethodCallExpression callExpression) { + LocalQuickFix[] fix = createNPEFixes(callExpression.getMethodExpression().getQualifierExpression(), callExpression); - holder.registerProblem(callExpression, - InspectionsBundle.message("dataflow.message.npe.method.invocation"), - fix); - } + holder.registerProblem(callExpression, + InspectionsBundle.message("dataflow.message.npe.method.invocation"), + fix); } - private void reportFieldAccessMayProduceNpe(ProblemsHolder holder, FieldReferenceInstruction frInstruction) { - PsiElement elementToAssert = frInstruction.getElementToAssert(); - PsiExpression expression = frInstruction.getExpression(); + private void reportFieldAccessMayProduceNpe(ProblemsHolder holder, PsiElement elementToAssert, PsiExpression expression) { if (expression instanceof PsiArrayAccessExpression) { LocalQuickFix[] fix = createNPEFixes((PsiExpression)elementToAssert, expression); holder.registerProblem(expression, @@ -382,8 +378,11 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { private static void reportCastMayFail(ProblemsHolder holder, TypeCastInstruction instruction) { PsiTypeCastExpression typeCast = instruction.getCastExpression(); - holder.registerProblem(typeCast.getCastType(), - InspectionsBundle.message("dataflow.message.cce", typeCast.getOperand().getText())); + PsiExpression operand = typeCast.getOperand(); + PsiTypeElement castType = typeCast.getCastType(); + assert castType != null; + assert operand != null; + holder.registerProblem(castType, InspectionsBundle.message("dataflow.message.cce", operand.getText())); } private void handleBranchingInstruction(ProblemsHolder holder, @@ -413,7 +412,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } else if (psiAnchor != null && !reportedAnchors.contains(psiAnchor) && !isCompileConstantInIfCondition(psiAnchor)) { boolean evaluatesToTrue = trueSet.contains(instruction); - if (onTheLeftSideOfConditionalAssignemnt(psiAnchor)) { + if (onTheLeftSideOfConditionalAssignment(psiAnchor)) { holder.registerProblem( psiAnchor, InspectionsBundle.message("dataflow.message.pointless.assignment.expression", Boolean.toString(evaluatesToTrue)), @@ -436,21 +435,20 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { visitor.silenceConstantCondition(psiAnchor); } - private void reportNullableArguments(StandardDataFlowRunner runner, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { - Set<PsiExpression> exprs = runner.getNullableArguments(); - for (PsiExpression expr : exprs) { + private void reportNullableArguments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { + for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableToNotNullParameter)) { if (!reportedAnchors.add(expr)) continue; final String text = isNullLiteralExpression(expr) ? InspectionsBundle.message("dataflow.message.passing.null.argument") : InspectionsBundle.message("dataflow.message.passing.nullable.argument"); - LocalQuickFix[] fixes = createNPEFixes(expr, expr); + LocalQuickFix[] fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr); holder.registerProblem(expr, text, fixes); } } - private static void reportNullableAssignments(StandardDataFlowRunner runner, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { - for (PsiExpression expr : runner.getNullableAssignments()) { + private static void reportNullableAssignments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { + for (PsiElement expr : visitor.getProblems(NullabilityProblem.assigningToNotNull)) { if (!reportedAnchors.add(expr)) continue; final String text = isNullLiteralExpression(expr) @@ -460,16 +458,16 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } - private static void reportUnboxedNullables(StandardDataFlowRunner runner, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { - for (PsiExpression expr : runner.getUnboxedNullables()) { + private static void reportUnboxedNullables(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { + for (PsiElement expr : visitor.getProblems(NullabilityProblem.unboxingNullable)) { if (!reportedAnchors.add(expr)) continue; holder.registerProblem(expr, InspectionsBundle.message("dataflow.message.unboxing")); } } - private static void reportNullableReturns(StandardDataFlowRunner runner, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { - for (PsiReturnStatement statement : runner.getNullableReturns()) { - final PsiExpression expr = statement.getReturnValue(); + private static void reportNullableReturns(StandardDataFlowRunner runner, DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set<PsiElement> reportedAnchors) { + for (PsiElement statement : visitor.getProblems(NullabilityProblem.nullableReturn)) { + final PsiExpression expr = ((PsiReturnStatement)statement).getReturnValue(); assert expr != null; if (!reportedAnchors.add(expr)) continue; @@ -547,7 +545,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { return parent instanceof PsiIfStatement && ((PsiIfStatement)parent).getCondition() == element; } - private static boolean isNullLiteralExpression(PsiExpression expr) { + private static boolean isNullLiteralExpression(PsiElement expr) { if (expr instanceof PsiLiteralExpression) { final PsiLiteralExpression literalExpression = (PsiLiteralExpression)expr; return PsiType.NULL.equals(literalExpression.getType()); @@ -555,7 +553,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { return false; } - private static boolean onTheLeftSideOfConditionalAssignemnt(final PsiElement psiAnchor) { + private static boolean onTheLeftSideOfConditionalAssignment(final PsiElement psiAnchor) { final PsiElement parent = psiAnchor.getParent(); if (parent instanceof PsiAssignmentExpression) { final PsiAssignmentExpression expression = (PsiAssignmentExpression)parent; @@ -699,45 +697,55 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { private static class DataFlowInstructionVisitor extends StandardInstructionVisitor { private final StandardDataFlowRunner myRunner; + private final MultiMap<NullabilityProblem, PsiElement> myProblems = new MultiMap<NullabilityProblem, PsiElement>(); + private final Map<Pair<NullabilityProblem, PsiElement>, StateInfo> myStateInfos = ContainerUtil.newHashMap(); private DataFlowInstructionVisitor(StandardDataFlowRunner runner) { myRunner = runner; } @Override - protected void onAssigningToNotNullableVariable(AssignInstruction instruction) { - myRunner.onAssigningToNotNullableVariable(instruction.getRExpression()); - } - - @Override - protected void onNullableReturn(CheckReturnValueInstruction instruction) { - myRunner.onNullableReturn(instruction.getReturn()); - } - - @Override protected void onInstructionProducesCCE(TypeCastInstruction instruction) { myRunner.onInstructionProducesCCE(instruction); } - - @Override - protected void onInstructionProducesNPE(Instruction instruction) { - if (instruction instanceof MethodCallInstruction && - ((MethodCallInstruction)instruction).getMethodType() == MethodCallInstruction.MethodType.UNBOXING) { - myRunner.onUnboxingNullable(((MethodCallInstruction)instruction).getContext()); - } - else { - myRunner.onInstructionProducesNPE(instruction); - } - } - - @Override - protected void onPassingNullParameter(PsiExpression arg) { - myRunner.onPassingNullParameter(arg); + + Collection<PsiElement> getProblems(final NullabilityProblem kind) { + return ContainerUtil.filter(myProblems.get(kind), new Condition<PsiElement>() { + @Override + public boolean value(PsiElement psiElement) { + StateInfo info = myStateInfos.get(Pair.create(kind, psiElement)); + // non-ephemeral NPE should be reported + // ephemeral NPE should also be reported if only ephemeral states have reached a particular problematic instruction + // (e.g. if it's inside "if (var == null)" check after contract method invocation + return info.normalNpe || info.ephemeralNpe && !info.normalOk; + } + }); } @Override - protected void onPassingNullParameterToNonAnnotated(DataFlowRunner runner, PsiExpression arg) { - myRunner.onPassingNullParameterToNonAnnotated(arg); + protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, NullabilityProblem problem, PsiElement anchor) { + boolean ok = super.checkNotNullable(state, value, problem, anchor); + if (!ok && anchor != null) { + myProblems.putValue(problem, anchor); + } + Pair<NullabilityProblem, PsiElement> key = Pair.create(problem, anchor); + StateInfo info = myStateInfos.get(key); + if (info == null) { + myStateInfos.put(key, info = new StateInfo()); + } + if (state.isEphemeral() && !ok) { + info.ephemeralNpe = true; + } else if (!state.isEphemeral()) { + if (ok) info.normalOk = true; + else info.normalNpe = true; + } + return ok; + } + + private static class StateInfo { + boolean ephemeralNpe; + boolean normalNpe; + boolean normalOk; } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java index 298b51bc4a24..38c242bc8e2a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java @@ -24,10 +24,7 @@ */ package com.intellij.codeInspection.dataFlow; -import com.intellij.codeInspection.dataFlow.instructions.BranchingInstruction; -import com.intellij.codeInspection.dataFlow.instructions.EmptyInstruction; -import com.intellij.codeInspection.dataFlow.instructions.Instruction; -import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction; +import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.openapi.application.ApplicationManager; @@ -38,7 +35,9 @@ import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; +import com.intellij.util.containers.MultiMapBasedOnSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -108,6 +107,17 @@ public class DataFlowRunner { myInstructions = flow.getInstructions(); myFields = flow.getFields(); myNestedClosures.clear(); + + Set<Instruction> joinInstructions = ContainerUtil.newHashSet(); + for (Instruction instruction : myInstructions) { + if (instruction instanceof GotoInstruction) { + joinInstructions.add(myInstructions[((GotoInstruction)instruction).getOffset()]); + } else if (instruction instanceof ConditionalGotoInstruction) { + joinInstructions.add(myInstructions[((ConditionalGotoInstruction)instruction).getOffset()]); + } else if (instruction instanceof GosubInstruction) { + joinInstructions.add(myInstructions[((GosubInstruction)instruction).getSubprogramOffset()]); + } + } if (LOG.isDebugEnabled()) { LOG.debug("Analyzing code block: " + psiBlock.getText()); @@ -123,47 +133,61 @@ public class DataFlowRunner { return RunnerResult.TOO_COMPLEX; } - final ArrayList<DfaInstructionState> queue = new ArrayList<DfaInstructionState>(); + final StateQueue queue = new StateQueue(); for (final DfaMemoryState initialState : initialStates) { - queue.add(new DfaInstructionState(myInstructions[0], initialState)); + queue.offer(new DfaInstructionState(myInstructions[0], initialState)); } + MultiMapBasedOnSet<BranchingInstruction, DfaMemoryState> processedStates = new MultiMapBasedOnSet<BranchingInstruction, DfaMemoryState>(); + MultiMapBasedOnSet<BranchingInstruction, DfaMemoryState> incomingStates = new MultiMapBasedOnSet<BranchingInstruction, DfaMemoryState>(); + WorkingTimeMeasurer measurer = new WorkingTimeMeasurer(shouldCheckTimeLimit() ? ourTimeLimit : ourTimeLimit * 42); int count = 0; while (!queue.isEmpty()) { - if (count % 64 == 0 && measurer.isTimeOver()) { - LOG.debug("Too complex because the analysis took too long"); - psiBlock.putUserData(TOO_EXPENSIVE_HASH, psiBlock.getText().hashCode()); - return RunnerResult.TOO_COMPLEX; - } - ProgressManager.checkCanceled(); - - DfaInstructionState instructionState = queue.remove(0); - if (LOG.isDebugEnabled()) { - LOG.debug(instructionState.toString()); - } - //System.out.println(instructionState.toString()); - - Instruction instruction = instructionState.getInstruction(); - long distance = instructionState.getDistanceFromStart(); + for (DfaInstructionState instructionState : queue.getNextInstructionStates(joinInstructions)) { + if (count++ % 1024 == 0 && measurer.isTimeOver()) { + LOG.debug("Too complex because the analysis took too long"); + psiBlock.putUserData(TOO_EXPENSIVE_HASH, psiBlock.getText().hashCode()); + return RunnerResult.TOO_COMPLEX; + } + ProgressManager.checkCanceled(); - if (instruction instanceof BranchingInstruction) { - if (!instruction.setMemoryStateProcessed(instructionState.getMemoryState().createCopy())) { - LOG.debug("Too complex because too many different possible states"); - return RunnerResult.TOO_COMPLEX; // Too complex :( + if (LOG.isDebugEnabled()) { + LOG.debug(instructionState.toString()); + } + //System.out.println(instructionState.toString()); + + Instruction instruction = instructionState.getInstruction(); + + if (instruction instanceof BranchingInstruction) { + BranchingInstruction branching = (BranchingInstruction)instruction; + if (processedStates.get(branching).contains(instructionState.getMemoryState())) { + continue; + } + if (processedStates.get(branching).size() > MAX_STATES_PER_BRANCH) { + LOG.debug("Too complex because too many different possible states"); + return RunnerResult.TOO_COMPLEX; // Too complex :( + } + processedStates.putValue(branching, instructionState.getMemoryState().createCopy()); } - } - DfaInstructionState[] after = acceptInstruction(visitor, instructionState); - for (DfaInstructionState state : after) { - Instruction nextInstruction = state.getInstruction(); - if ((!(nextInstruction instanceof BranchingInstruction) || !nextInstruction.isMemoryStateProcessed(state.getMemoryState())) && instruction.getIndex() < endOffset) { - state.setDistanceFromStart(distance + 1); - queue.add(state); + DfaInstructionState[] after = acceptInstruction(visitor, instructionState); + for (DfaInstructionState state : after) { + Instruction nextInstruction = state.getInstruction(); + if (nextInstruction.getIndex() >= endOffset) { + continue; + } + if (nextInstruction instanceof BranchingInstruction) { + BranchingInstruction branching = (BranchingInstruction)nextInstruction; + if (processedStates.get(branching).contains(state.getMemoryState()) || + incomingStates.get(branching).contains(state.getMemoryState())) { + continue; + } + incomingStates.putValue(branching, state.getMemoryState().createCopy()); + } + queue.offer(state); } } - - count++; } psiBlock.putUserData(TOO_EXPENSIVE_HASH, null); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java index 87d8b8a1da50..1683807450ab 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaInstructionState.java @@ -25,23 +25,26 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.instructions.Instruction; +import com.intellij.openapi.util.Pair; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -public class DfaInstructionState { +import java.util.Collections; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Set; + +public class DfaInstructionState implements Comparable<DfaInstructionState> { public static final DfaInstructionState[] EMPTY_ARRAY = new DfaInstructionState[0]; private final DfaMemoryState myBeforeMemoryState; private final Instruction myInstruction; - private long myDistanceFromStart = 0; public DfaInstructionState(@NotNull Instruction myInstruction, @NotNull DfaMemoryState myBeforeMemoryState) { this.myBeforeMemoryState = myBeforeMemoryState; this.myInstruction = myInstruction; } - public long getDistanceFromStart() { return myDistanceFromStart; } - - public void setDistanceFromStart(long distanceFromStart) { myDistanceFromStart = distanceFromStart; } - @NotNull public Instruction getInstruction() { return myInstruction; @@ -55,4 +58,58 @@ public class DfaInstructionState { public String toString() { return getInstruction().getIndex() + " " + getInstruction() + ": " + getMemoryState().toString(); } + + @Override + public int compareTo(@NotNull DfaInstructionState o) { + return myInstruction.getIndex() - o.myInstruction.getIndex(); + } } + +class StateQueue { + private final PriorityQueue<DfaInstructionState> myQueue = new PriorityQueue<DfaInstructionState>(); + private final Set<Pair<Instruction, DfaMemoryState>> mySet = ContainerUtil.newHashSet(); + + void offer(DfaInstructionState state) { + if (mySet.add(Pair.create(state.getInstruction(), state.getMemoryState()))) { + myQueue.offer(state); + } + } + + boolean isEmpty() { + return myQueue.isEmpty(); + } + + List<DfaInstructionState> getNextInstructionStates(Set<Instruction> joinInstructions) { + DfaInstructionState state = myQueue.poll(); + final Instruction instruction = state.getInstruction(); + mySet.remove(Pair.create(instruction, state.getMemoryState())); + + DfaInstructionState next = myQueue.peek(); + if (next == null || next.compareTo(state) != 0) return Collections.singletonList(state); + + List<DfaMemoryStateImpl> memoryStates = ContainerUtil.newArrayList(); + memoryStates.add((DfaMemoryStateImpl)state.getMemoryState()); + while (!myQueue.isEmpty() && myQueue.peek().compareTo(state) == 0) { + DfaMemoryState anotherState = myQueue.poll().getMemoryState(); + mySet.remove(Pair.create(instruction, anotherState)); + memoryStates.add((DfaMemoryStateImpl)anotherState); + } + + if (memoryStates.size() > 1 && joinInstructions.contains(instruction)) { + while (true) { + List<DfaMemoryStateImpl> nextStates = new StateMerger(memoryStates).merge(); + if (nextStates == null) break; + memoryStates = nextStates; + } + } + + return ContainerUtil.map(memoryStates, new Function<DfaMemoryStateImpl, DfaInstructionState>() { + @Override + public DfaInstructionState fun(DfaMemoryStateImpl state) { + return new DfaInstructionState(instruction, state); + } + }); + } + + +}
\ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java index 8309670020ab..106267affda4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java @@ -59,4 +59,13 @@ public interface DfaMemoryState { @Nullable DfaConstValue getConstantValue(DfaVariableValue value); + + /** + * Ephemeral means a state that was created when considering a method contract and checking if one of its arguments is null. + * With explicit null check, that would result in any non-annotated variable being treated as nullable and producing possible NPE warnings later. + * With contracts, we don't want this. So the state where this variable is null is marked ephemeral and no NPE warnings are issued for such states. + */ + void markEphemeral(); + + boolean isEphemeral(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index d77a0f985985..f5430d5fd713 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -26,6 +26,7 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.UnorderedPair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.TypeConversionUtil; @@ -41,43 +42,50 @@ import java.util.*; public class DfaMemoryStateImpl implements DfaMemoryState { private final DfaValueFactory myFactory; - private final List<SortedIntSet> myEqClasses = new ArrayList<SortedIntSet>(); - private final Stack<DfaValue> myStack = new Stack<DfaValue>(); - private TIntStack myOffsetStack = new TIntStack(1); - private final TLongHashSet myDistinctClasses = new TLongHashSet(); - private final THashMap<DfaVariableValue,DfaVariableState> myVariableStates = new THashMap<DfaVariableValue, DfaVariableState>(); - private final THashSet<DfaVariableValue> myUnknownVariables = new THashSet<DfaVariableValue>(); + private final List<EqClass> myEqClasses; + private final Stack<DfaValue> myStack; + private TIntStack myOffsetStack; + private final TLongHashSet myDistinctClasses; + private final Map<DfaVariableValue,DfaVariableState> myVariableStates; + private final Map<DfaVariableValue,DfaVariableState> myDefaultVariableStates; + private final Set<DfaVariableValue> myUnknownVariables; + private boolean myEphemeral; public DfaMemoryStateImpl(final DfaValueFactory factory) { myFactory = factory; + myDefaultVariableStates = ContainerUtil.newTroveMap(); + myEqClasses = ContainerUtil.newArrayList(); + myUnknownVariables = ContainerUtil.newTroveSet(); + myVariableStates = ContainerUtil.newTroveMap(); + myDistinctClasses = new TLongHashSet(); + myOffsetStack = new TIntStack(); + myStack = new Stack<DfaValue>(); + } + + protected DfaMemoryStateImpl(DfaMemoryStateImpl toCopy) { + myFactory = toCopy.myFactory; + myEphemeral = toCopy.myEphemeral; + myDefaultVariableStates = toCopy.myDefaultVariableStates; // shared between all states + + myStack = new Stack<DfaValue>(toCopy.myStack); + myDistinctClasses = new TLongHashSet(toCopy.myDistinctClasses.toArray()); + myUnknownVariables = new THashSet<DfaVariableValue>(toCopy.myUnknownVariables); + myOffsetStack = toCopy.myOffsetStack; + + myEqClasses = ContainerUtil.newArrayList(toCopy.myEqClasses); + myVariableStates = new THashMap<DfaVariableValue, DfaVariableState>(toCopy.myVariableStates); + + myCachedDistinctClassPairs = toCopy.myCachedDistinctClassPairs; + myCachedNonTrivialEqClasses = toCopy.myCachedNonTrivialEqClasses; } public DfaValueFactory getFactory() { return myFactory; } - protected DfaMemoryStateImpl createNew() { - return new DfaMemoryStateImpl(myFactory); - } - @Override public DfaMemoryStateImpl createCopy() { - DfaMemoryStateImpl newState = createNew(); - - newState.myStack.addAll(myStack); - newState.myDistinctClasses.addAll(myDistinctClasses.toArray()); - newState.myUnknownVariables.addAll(myUnknownVariables); - newState.myOffsetStack = new TIntStack(myOffsetStack); - - for (int i = 0; i < myEqClasses.size(); i++) { - SortedIntSet aClass = myEqClasses.get(i); - newState.myEqClasses.add(aClass != null ? new SortedIntSet(aClass.toNativeArray()) : null); - } - - for (DfaVariableValue dfaVariableValue : myVariableStates.keySet()) { - newState.myVariableStates.put(dfaVariableValue, myVariableStates.get(dfaVariableValue).clone()); - } - return newState; + return new DfaMemoryStateImpl(this); } public boolean equals(Object obj) { @@ -85,84 +93,70 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (!(obj instanceof DfaMemoryStateImpl)) return false; DfaMemoryStateImpl that = (DfaMemoryStateImpl)obj; - if (myDistinctClasses.size() != that.myDistinctClasses.size()) return false; - if (myStack.size() != that.myStack.size()) return false; - if (myOffsetStack.size() != that.myOffsetStack.size()) return false; - if (myVariableStates.size() != that.myVariableStates.size()) return false; - if (myUnknownVariables.size() != that.myUnknownVariables.size()) return false; + return equalsSuperficially(that) && myUnknownVariables.equals(that.myUnknownVariables) && equalsByRelations(that); + } - if (!myStack.equals(that.myStack)) return false; - if (!myOffsetStack.equals(that.myOffsetStack)) return false; - if (!myVariableStates.equals(that.myVariableStates)) return false; - if (!myUnknownVariables.equals(that.myUnknownVariables)) return false; - + boolean equalsSuperficially(DfaMemoryStateImpl other) { + return myEphemeral == other.myEphemeral && myStack.equals(other.myStack) && myOffsetStack.equals(other.myOffsetStack); + } + + boolean equalsByRelations(DfaMemoryStateImpl that) { + if (myDistinctClasses.size() != that.myDistinctClasses.size()) return false; if (!getNonTrivialEqClasses().equals(that.getNonTrivialEqClasses())) return false; if (!getDistinctClassPairs().equals(that.getDistinctClassPairs())) return false; - + if (!myVariableStates.equals(that.myVariableStates)) return false; return true; } - private Set<Set<SortedIntSet>> getDistinctClassPairs() { - Set<Set<SortedIntSet>> result = ContainerUtil.newHashSet(); + private Set<UnorderedPair<EqClass>> myCachedDistinctClassPairs; + Set<UnorderedPair<EqClass>> getDistinctClassPairs() { + if (myCachedDistinctClassPairs != null) return myCachedDistinctClassPairs; + + Set<UnorderedPair<EqClass>> result = ContainerUtil.newHashSet(); for (long encodedPair : myDistinctClasses.toArray()) { - THashSet<SortedIntSet> pair = new THashSet<SortedIntSet>(2); - pair.add(myEqClasses.get(low(encodedPair))); - pair.add(myEqClasses.get(high(encodedPair))); - result.add(pair); + result.add(new UnorderedPair<EqClass>(myEqClasses.get(low(encodedPair)), myEqClasses.get(high(encodedPair)))); } - return result; + return myCachedDistinctClassPairs = result; } - private Set<SortedIntSet> getNonTrivialEqClasses() { - Set<SortedIntSet> result = ContainerUtil.newHashSet(); - for (SortedIntSet eqClass : myEqClasses) { + private Set<EqClass> myCachedNonTrivialEqClasses; + Set<EqClass> getNonTrivialEqClasses() { + if (myCachedNonTrivialEqClasses != null) return myCachedNonTrivialEqClasses; + + Set<EqClass> result = ContainerUtil.newHashSet(); + for (EqClass eqClass : myEqClasses) { if (eqClass != null && eqClass.size() > 1) { result.add(eqClass); } } - return result; + return myCachedNonTrivialEqClasses = result; } public int hashCode() { - return 0; - //return ((myEqClasses.hashCode() * 31 + myStack.hashCode()) * 31 + myVariableStates.hashCode()) * 31 + myUnknownVariables.hashCode(); - } - - private void appendClass(StringBuilder buf, @Nullable SortedIntSet aClass) { - if (aClass == null) return; - - buf.append("("); - - for (int i = 0; i < aClass.size(); i++) { - if (i > 0) buf.append(", "); - int value = aClass.get(i); - DfaValue dfaValue = myFactory.getValue(value); - buf.append(dfaValue); - } - buf.append(")"); + return (((getNonTrivialEqClasses().hashCode() * 31 + + getDistinctClassPairs().hashCode()) * 31 + + myStack.hashCode()) * 31 + + myUnknownVariables.hashCode()) * 31 + + myVariableStates.hashCode(); } @SuppressWarnings({"HardCodedStringLiteral"}) public String toString() { StringBuilder result = new StringBuilder(); result.append('<'); + if (myEphemeral) { + result.append("ephemeral, "); + } - for (SortedIntSet set : getNonTrivialEqClasses()) { - appendClass(result, set); + for (EqClass set : getNonTrivialEqClasses()) { + result.append(set); } if (!myDistinctClasses.isEmpty()) { result.append("\n distincts: "); List<String> distincts = new ArrayList<String>(); - for (Set<SortedIntSet> pair : getDistinctClassPairs()) { - ArrayList<SortedIntSet> list = new ArrayList<SortedIntSet>(pair); - StringBuilder one = new StringBuilder(); - one.append("{"); - appendClass(one, list.get(0)); - one.append(", "); - appendClass(one, list.get(1)); - one.append("}"); - distincts.add(one.toString()); + for (UnorderedPair<EqClass> pair : getDistinctClassPairs()) { + distincts.add("{" + pair.first + ", " + pair.second + "}"); } Collections.sort(distincts); result.append(StringUtil.join(distincts, " ")); @@ -174,7 +168,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (!myVariableStates.isEmpty()) { result.append("\n vars: "); for (Map.Entry<DfaVariableValue, DfaVariableState> entry : myVariableStates.entrySet()) { - result.append("\n[").append(entry.getKey()).append("->").append(entry.getValue()).append("]"); + result.append("[").append(entry.getKey()).append("->").append(entry.getValue()).append("] "); } } if (!myUnknownVariables.isEmpty()) { @@ -201,11 +195,13 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override public int popOffset() { + myOffsetStack = new TIntStack(myOffsetStack); return myOffsetStack.pop(); } @Override public void pushOffset(int offset) { + myOffsetStack = new TIntStack(myOffsetStack); myOffsetStack.push(offset); } @@ -220,13 +216,13 @@ public class DfaMemoryStateImpl implements DfaMemoryState { flushVariable(var); if (value instanceof DfaUnknownValue) { - getVariableState(var).setNullable(false); + setVariableState(var, getVariableState(var).withNullable(false)); return; } - getVariableState(var).setValue(value); + setVariableState(var, getVariableState(var).withValue(value)); if (value instanceof DfaTypeValue) { - getVariableState(var).setNullable(((DfaTypeValue)value).isNullable()); + setVariableState(var, getVariableState(var).withNullable(((DfaTypeValue)value).isNullable())); DfaRelationValue dfaInstanceof = myFactory.getRelationFactory().createRelation(var, value, JavaTokenType.INSTANCEOF_KEYWORD, false); if (((DfaTypeValue)value).isNotNull()) { applyCondition(dfaInstanceof); @@ -240,10 +236,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState { applyCondition(dfaEqual); if (value instanceof DfaVariableValue) { - myVariableStates.put(var, getVariableState((DfaVariableValue)value).clone()); + setVariableState(var, getVariableState((DfaVariableValue)value)); } else if (value instanceof DfaBoxedValue) { - getVariableState(var).setNullable(false); + setVariableState(var, getVariableState(var).withNullable(false)); applyCondition(compareToNull(var, true)); } } @@ -260,7 +256,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (!canBeReused(dfaValue) && !(((DfaBoxedValue)dfaValue).getWrappedValue() instanceof DfaConstValue)) { return null; } - SortedIntSet aClass = new SortedIntSet(); + EqClass aClass = new EqClass(myFactory); aClass.add(dfaValue.getID()); myEqClasses.add(aClass); @@ -270,20 +266,11 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @NotNull private List<DfaValue> getEqClassesFor(@NotNull DfaValue dfaValue) { int index = getEqClassIndex(dfaValue); - SortedIntSet set = index == -1 ? null : myEqClasses.get(index); + EqClass set = index == -1 ? null : myEqClasses.get(index); if (set == null) { return Collections.emptyList(); } - final List<DfaValue> result = new ArrayList<DfaValue>(set.size()); - set.forEach(new TIntProcedure() { - @Override - public boolean execute(int c1) { - DfaValue value = myFactory.getValue(c1); - result.add(value); - return true; - } - }); - return result; + return set.getMemberValues(); } private boolean canBeNaN(@NotNull DfaValue dfaValue) { @@ -309,7 +296,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { private int getEqClassIndex(@NotNull DfaValue dfaValue) { for (int i = 0; i < myEqClasses.size(); i++) { - SortedIntSet aClass = myEqClasses.get(i); + EqClass aClass = myEqClasses.get(i); if (aClass != null && aClass.contains(dfaValue.getID())) { if (!canBeReused(dfaValue) && aClass.size() > 1) return -1; return i; @@ -368,8 +355,8 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } private boolean uniteClasses(int c1Index, int c2Index) { - SortedIntSet c1 = myEqClasses.get(c1Index); - SortedIntSet c2 = myEqClasses.get(c2Index); + EqClass c1 = myEqClasses.get(c1Index); + EqClass c2 = myEqClasses.get(c2Index); Set<DfaVariableValue> vars = ContainerUtil.newTroveSet(); Set<DfaVariableValue> negatedVars = ContainerUtil.newTroveSet(); @@ -379,15 +366,14 @@ public class DfaMemoryStateImpl implements DfaMemoryState { int nConst = 0; for (int c : cs) { - DfaValue dfaValue = myFactory.getValue(c); - if (dfaValue instanceof DfaBoxedValue) dfaValue = ((DfaBoxedValue)dfaValue).getWrappedValue(); - if (dfaValue instanceof DfaUnboxedValue) dfaValue = ((DfaUnboxedValue)dfaValue).getVariable(); + DfaValue dfaValue = unwrap(myFactory.getValue(c)); if (dfaValue instanceof DfaConstValue) nConst++; if (dfaValue instanceof DfaVariableValue) { DfaVariableValue variableValue = (DfaVariableValue)dfaValue; if (variableValue.isNegated()) { negatedVars.add(variableValue.createNegated()); - } else { + } + else { vars.add(variableValue); } } @@ -412,6 +398,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } } + myEqClasses.set(c1Index, c1 = new EqClass(c1)); for (int i = 0; i < c2.size(); i++) { int c = c2.get(i); c1.add(c); @@ -458,7 +445,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { public boolean isNull(DfaValue dfaValue) { if (dfaValue instanceof DfaTypeValue && ((DfaTypeValue)dfaValue).isNotNull()) return false; - if (dfaValue instanceof DfaConstValue) return ((DfaConstValue)dfaValue).getConstant() == null; + if (dfaValue instanceof DfaConstValue) return ((DfaConstValue)dfaValue).getValue() == null; if (dfaValue instanceof DfaVariableValue) { int c1Index = getEqClassIndex(dfaValue); @@ -495,33 +482,47 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override @Nullable public DfaConstValue getConstantValue(DfaVariableValue value) { - DfaConstValue result = null; - for (DfaValue equal : getEqClassesFor(value)) { - if (equal instanceof DfaVariableValue) continue; - DfaConstValue constValue = asConstantValue(equal); - if (constValue == null) return null; - result = constValue; - } - return result; + int index = getEqClassIndex(value); + EqClass ec = index == -1 ? null : myEqClasses.get(index); + return ec == null ? null : ec.findConstant(true); + } + + @Override + public void markEphemeral() { + myEphemeral = true; + } + + @Override + public boolean isEphemeral() { + return myEphemeral; } @Override public boolean applyInstanceofOrNull(DfaRelationValue dfaCond) { - DfaValue left = dfaCond.getLeftOperand(); - if (left instanceof DfaBoxedValue) { - left = ((DfaBoxedValue)left).getWrappedValue(); - } - else if (left instanceof DfaUnboxedValue) { - left = ((DfaUnboxedValue)left).getVariable(); - } + DfaValue left = unwrap(dfaCond.getLeftOperand()); if (!(left instanceof DfaVariableValue)) return true; DfaVariableValue dfaVar = (DfaVariableValue)left; DfaTypeValue dfaType = (DfaTypeValue)dfaCond.getRightOperand(); - final DfaVariableState varState = getVariableState(dfaVar); - return isNull(dfaVar) || varState.setInstanceofValue(dfaType); + if (isUnknownState(dfaVar) || isNull(dfaVar)) return true; + DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue(dfaType); + if (newState != null) { + setVariableState(dfaVar, newState); + return true; + } + return false; + } + + static DfaValue unwrap(DfaValue value) { + if (value instanceof DfaBoxedValue) { + return ((DfaBoxedValue)value).getWrappedValue(); + } + else if (value instanceof DfaUnboxedValue) { + return ((DfaUnboxedValue)value).getVariable(); + } + return value; } @Override @@ -563,12 +564,25 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (dfaRight instanceof DfaTypeValue) { if (dfaLeft instanceof DfaVariableValue) { - DfaVariableState varState = getVariableState((DfaVariableValue)dfaLeft); DfaVariableValue dfaVar = (DfaVariableValue)dfaLeft; + if (isUnknownState(dfaVar)) return true; + if (isNegated) { - return varState.addNotInstanceofValue((DfaTypeValue)dfaRight) || applyCondition(compareToNull(dfaVar, false)); + DfaVariableState newState = getVariableState(dfaVar).withNotInstanceofValue((DfaTypeValue)dfaRight); + if (newState != null) { + setVariableState(dfaVar, newState); + return true; + } + return applyCondition(compareToNull(dfaVar, false)); + } + if (applyCondition(compareToNull(dfaVar, true))) { + DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue((DfaTypeValue)dfaRight); + if (newState != null) { + setVariableState(dfaVar, newState); + return true; + } } - return applyCondition(compareToNull(dfaVar, true)) && varState.setInstanceofValue((DfaTypeValue)dfaRight); + return false; } return true; } @@ -595,7 +609,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (isNotNull(dfaVar)) { return true; } - getVariableState(dfaVar).setNullable(true); + if (!isUnknownState(dfaVar)) { + setVariableState(dfaVar, getVariableState(dfaVar).withNullability(Nullness.NULLABLE)); + } } return false; } @@ -637,7 +653,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (!TypeConversionUtil.isPrimitiveWrapper(type)) { return true; } - if (negated && !isMaybeBoxedConstant(dfaRight)) { + if (negated && !(unwrap(dfaRight) instanceof DfaConstValue)) { // from the fact (wrappers are not the same) does not follow (unboxed values are not equals) return true; } @@ -646,11 +662,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return applyRelation(boxedFactory.createUnboxed(dfaLeft), boxedFactory.createUnboxed(dfaRight), negated); } - private static boolean isMaybeBoxedConstant(DfaValue val) { - return val instanceof DfaConstValue || - val instanceof DfaBoxedValue && ((DfaBoxedValue)val).getWrappedValue() instanceof DfaConstValue; - } - private boolean checkCompareWithBooleanLiteral(DfaValue dfaLeft, DfaValue dfaRight, boolean negated) { if (dfaRight instanceof DfaConstValue) { Object constVal = ((DfaConstValue)dfaRight).getValue(); @@ -691,18 +702,28 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (!isNegated) { //Equals if (c1Index.equals(c2Index)) return true; if (!uniteClasses(c1Index, c2Index)) return false; + + for (long encodedPair : myDistinctClasses.toArray()) { + EqClass c1 = myEqClasses.get(low(encodedPair)); + EqClass c2 = myEqClasses.get(high(encodedPair)); + if (c1.findConstant(false) != null && c2.findConstant(false) != null) { + myDistinctClasses.remove(encodedPair); + } + } + myCachedDistinctClassPairs = null; + myCachedNonTrivialEqClasses = null; } else { // Not Equals if (c1Index.equals(c2Index)) return false; makeClassesDistinct(c1Index, c2Index); + myCachedDistinctClassPairs = null; } return true; } private boolean isUnknownState(DfaValue val) { - if (val instanceof DfaBoxedValue) return isUnknownState(((DfaBoxedValue)val).getWrappedValue()); - if (val instanceof DfaUnboxedValue) return isUnknownState(((DfaUnboxedValue)val).getVariable()); + val = unwrap(val); if (val instanceof DfaVariableValue) { if (myUnknownVariables.contains(val)) return true; DfaVariableValue negatedValue = ((DfaVariableValue)val).getNegatedValue(); @@ -731,17 +752,33 @@ public class DfaMemoryStateImpl implements DfaMemoryState { return myFactory.getRelationFactory().createRelation(dfaVar, dfaNull, JavaTokenType.EQEQ, negated); } + void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) { + assert !myUnknownVariables.contains(dfaVar); + if (state.equals(myDefaultVariableStates.get(dfaVar))) { + myVariableStates.remove(dfaVar); + } else { + myVariableStates.put(dfaVar, state); + } + } + public DfaVariableState getVariableState(DfaVariableValue dfaVar) { DfaVariableState state = myVariableStates.get(dfaVar); if (state == null) { - state = createVariableState(dfaVar); + state = myDefaultVariableStates.get(dfaVar); + if (state == null) { + state = createVariableState(dfaVar); + DfaTypeValue initialType = dfaVar.getTypeValue(); + if (initialType != null) { + state = state.withInstanceofValue(initialType); + assert state != null; + } + myDefaultVariableStates.put(dfaVar, state); + } + if (isUnknownState(dfaVar)) { - state.setNullable(false); - return state; + return state.withNullable(false); } - - myVariableStates.put(dfaVar, state); } return state; @@ -757,28 +794,25 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override public void flushFields(DfaVariableValue[] fields) { - Set<DfaVariableValue> allVars = new HashSet<DfaVariableValue>(myVariableStates.keySet()); - Collections.addAll(allVars, fields); - - Set<DfaVariableValue> dependencies = new HashSet<DfaVariableValue>(); - for (DfaVariableValue variableValue : allVars) { - dependencies.addAll(myFactory.getVarFactory().getAllQualifiedBy(variableValue)); - } - allVars.addAll(dependencies); - - for (DfaVariableValue value : allVars) { - if (myVariableStates.containsKey(value) || getEqClassIndex(value) >= 0) { - if (value.isFlushableByCalls()) { - doFlush(value); - myUnknownVariables.add(value); + for (EqClass aClass : myEqClasses) { + if (aClass != null) { + for (DfaVariableValue value : aClass.getVariables()) { + if (value.isFlushableByCalls()) { + doFlush(value, true); + } } } } + for (DfaVariableValue value : new ArrayList<DfaVariableValue>(myVariableStates.keySet())) { + if (value.isFlushableByCalls()) { + doFlush(value, true); + } + } } @Override public void flushVariable(@NotNull DfaVariableValue variable) { - doFlush(variable); + doFlush(variable, false); flushDependencies(variable); myUnknownVariables.remove(variable); myUnknownVariables.removeAll(myFactory.getVarFactory().getAllQualifiedBy(variable)); @@ -786,11 +820,15 @@ public class DfaMemoryStateImpl implements DfaMemoryState { public void flushDependencies(DfaVariableValue variable) { for (DfaVariableValue dependent : myFactory.getVarFactory().getAllQualifiedBy(variable)) { - doFlush(dependent); + doFlush(dependent, false); } } - private void doFlush(DfaVariableValue varPlain) { + Set<DfaVariableValue> getUnknownVariables() { + return myUnknownVariables; + } + + void doFlush(DfaVariableValue varPlain, boolean markUnknown) { DfaVariableValue varNegated = varPlain.getNegatedValue(); final int idPlain = varPlain.getID(); @@ -799,7 +837,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { int size = myEqClasses.size(); int interruptCount = 0; for (int varClassIndex = 0; varClassIndex < size; varClassIndex++) { - final SortedIntSet varClass = myEqClasses.get(varClassIndex); + EqClass varClass = myEqClasses.get(varClassIndex); if (varClass == null) continue; for (int i = 0; i < varClass.size(); i++) { @@ -809,6 +847,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { int cl = varClass.get(i); DfaValue value = myFactory.getValue(cl); if (mine(idPlain, value) || idNegated >= 0 && mine(idNegated, value)) { + myEqClasses.set(varClassIndex, varClass = new EqClass(varClass)); varClass.remove(i); break; } @@ -823,10 +862,10 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } } } - else if (containsConstantsOnly(varClassIndex)) { + else if (varClass.containsConstantsOnly()) { for (long pair : myDistinctClasses.toArray()) { - if (low(pair) == varClassIndex && containsConstantsOnly(high(pair)) || - high(pair) == varClassIndex && containsConstantsOnly(low(pair))) { + if (low(pair) == varClassIndex && myEqClasses.get(high(pair)).containsConstantsOnly() || + high(pair) == varClassIndex && myEqClasses.get(low(pair)).containsConstantsOnly()) { myDistinctClasses.remove(pair); } } @@ -837,28 +876,14 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (varNegated != null) { myVariableStates.remove(varNegated); } - } - - @Nullable private static DfaConstValue asConstantValue(DfaValue value) { - if (value instanceof DfaConstValue) return (DfaConstValue)value; - if (value instanceof DfaBoxedValue && ((DfaBoxedValue)value).getWrappedValue() instanceof DfaConstValue) return (DfaConstValue)((DfaBoxedValue)value).getWrappedValue(); - return null; - } - - private boolean containsConstantsOnly(int id) { - SortedIntSet varClass = myEqClasses.get(id); - for (int i = 0; i < varClass.size(); i++) { - if (asConstantValue(myFactory.getValue(varClass.get(i))) == null) { - return false; - } + if (markUnknown) { + myUnknownVariables.add(varPlain); } - - return true; + myCachedNonTrivialEqClasses = null; + myCachedDistinctClassPairs = null; } private static boolean mine(int id, DfaValue value) { - return value != null && id == value.getID() || - value instanceof DfaBoxedValue && ((DfaBoxedValue)value).getWrappedValue().getID() == id || - value instanceof DfaUnboxedValue && ((DfaUnboxedValue)value).getVariable().getID() == id; + return value != null && id == unwrap(value).getID(); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java index 3b493d4dea26..8ed7836f0dfd 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java @@ -37,9 +37,6 @@ import java.util.List; import java.util.Set; public class DfaPsiUtil { - public static boolean isPlainMutableField(PsiVariable var) { - return !var.hasModifierProperty(PsiModifier.FINAL) && !var.hasModifierProperty(PsiModifier.TRANSIENT) && !var.hasModifierProperty(PsiModifier.VOLATILE) && var instanceof PsiField; - } public static boolean isFinalField(PsiVariable var) { return var.hasModifierProperty(PsiModifier.FINAL) && !var.hasModifierProperty(PsiModifier.TRANSIENT) && var instanceof PsiField; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java index 8c3cce8a51ea..16a2a6d8e3a7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java @@ -163,9 +163,10 @@ public class DfaUtil { } @Override - public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState _memState) { final Instruction nextInstruction = runner.getInstruction(instruction.getIndex() + 1); + ValuableDataFlowRunner.MyDfaMemoryState memState = (ValuableDataFlowRunner.MyDfaMemoryState)_memState; final DfaValue dfaSource = memState.pop(); final DfaValue dfaDest = memState.pop(); @@ -176,10 +177,10 @@ public class DfaUtil { final IElementType type = parent instanceof PsiAssignmentExpression ? ((PsiAssignmentExpression)parent).getOperationTokenType() : JavaTokenType.EQ; // store current value - to use in case of '+=' - final PsiExpression prevValue = ((ValuableDataFlowRunner.ValuableDfaVariableState)((ValuableDataFlowRunner.MyDfaMemoryState)memState).getVariableState(var)).myExpression; + final PsiExpression prevValue = ((ValuableDataFlowRunner.ValuableDfaVariableState)memState.getVariableState(var)).myExpression; memState.setVarValue(var, dfaSource); // state may have been changed so re-retrieve it - final ValuableDataFlowRunner.ValuableDfaVariableState curState = (ValuableDataFlowRunner.ValuableDfaVariableState)((ValuableDataFlowRunner.MyDfaMemoryState)memState).getVariableState(var); + final ValuableDataFlowRunner.ValuableDfaVariableState curState = (ValuableDataFlowRunner.ValuableDfaVariableState)memState.getVariableState(var); final PsiExpression curValue = curState.myExpression; final PsiExpression nextValue; if (type == JavaTokenType.PLUSEQ && prevValue != null) { @@ -196,7 +197,7 @@ public class DfaUtil { else { nextValue = curValue == null ? rightValue : curValue; } - curState.myExpression = nextValue; + memState.setVariableState(var, curState.withExpression(nextValue)); } memState.push(dfaDest); return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, memState)}; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java index 81738226061a..61d61c26cf79 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaVariableState.java @@ -24,138 +24,134 @@ */ package com.intellij.codeInspection.dataFlow; +import com.intellij.codeInspection.dataFlow.value.DfaPsiType; import com.intellij.codeInspection.dataFlow.value.DfaTypeValue; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; -import com.intellij.psi.*; -import gnu.trove.THashSet; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiPrimitiveType; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; -public class DfaVariableState implements Cloneable { - private final Set<DfaTypeValue> myInstanceofValues; - private final Set<DfaTypeValue> myNotInstanceofValues; - private Nullness myNullability; +public class DfaVariableState { + protected final Set<DfaPsiType> myInstanceofValues; + protected final Set<DfaPsiType> myNotInstanceofValues; + protected final Nullness myNullability; public DfaVariableState(@NotNull DfaVariableValue dfaVar) { - myInstanceofValues = new HashSet<DfaTypeValue>(); - myNotInstanceofValues = new HashSet<DfaTypeValue>(); - - myNullability = dfaVar.getInherentNullability(); - DfaTypeValue initialType = dfaVar.getTypeValue(); - if (initialType != null) { - setInstanceofValue(initialType); - } + this(Collections.<DfaPsiType>emptySet(), Collections.<DfaPsiType>emptySet(), dfaVar.getInherentNullability()); } - protected DfaVariableState(final DfaVariableState toClone) { - myInstanceofValues = new THashSet<DfaTypeValue>(toClone.myInstanceofValues); - myNotInstanceofValues = new THashSet<DfaTypeValue>(toClone.myNotInstanceofValues); - myNullability = toClone.myNullability; + protected DfaVariableState(Set<DfaPsiType> instanceofValues, + Set<DfaPsiType> notInstanceofValues, Nullness nullability) { + myInstanceofValues = instanceofValues; + myNotInstanceofValues = notInstanceofValues; + myNullability = nullability; } public boolean isNullable() { return myNullability == Nullness.NULLABLE; } - private boolean checkInstanceofValue(DfaTypeValue dfaType) { + private boolean checkInstanceofValue(DfaPsiType dfaType) { if (myInstanceofValues.contains(dfaType)) return true; - for (DfaTypeValue dfaTypeValue : myNotInstanceofValues) { + for (DfaPsiType dfaTypeValue : myNotInstanceofValues) { if (dfaTypeValue.isAssignableFrom(dfaType)) return false; } - for (DfaTypeValue dfaTypeValue : myInstanceofValues) { + for (DfaPsiType dfaTypeValue : myInstanceofValues) { if (!dfaType.isConvertibleFrom(dfaTypeValue)) return false; } return true; } - public boolean setInstanceofValue(DfaTypeValue dfaType) { - if (dfaType.isNullable()) { - myNullability = Nullness.NULLABLE; - } - - if (dfaType.getType() instanceof PsiPrimitiveType) return true; - - if (checkInstanceofValue(dfaType)) { - myInstanceofValues.add(dfaType); - return true; + @Nullable + public DfaVariableState withInstanceofValue(DfaTypeValue dfaType) { + if (dfaType.getDfaType().getPsiType() instanceof PsiPrimitiveType) return this; + + if (checkInstanceofValue(dfaType.getDfaType())) { + DfaVariableState result = dfaType.isNullable() ? withNullability(Nullness.NULLABLE) : this; + if (!myInstanceofValues.contains(dfaType.getDfaType())) { + HashSet<DfaPsiType> newInstanceof = ContainerUtil.newHashSet(myInstanceofValues); + newInstanceof.add(dfaType.getDfaType()); + result = createCopy(newInstanceof, myNotInstanceofValues, result.myNullability); + } + return result; } - return false; + return null; } - public boolean addNotInstanceofValue(DfaTypeValue dfaType) { - if (myNotInstanceofValues.contains(dfaType)) return true; + @Nullable + public DfaVariableState withNotInstanceofValue(DfaTypeValue dfaType) { + if (myNotInstanceofValues.contains(dfaType.getDfaType())) return this; - for (DfaTypeValue dfaTypeValue : myInstanceofValues) { - if (dfaType.isAssignableFrom(dfaTypeValue)) return false; + for (DfaPsiType dfaTypeValue : myInstanceofValues) { + if (dfaType.getDfaType().isAssignableFrom(dfaTypeValue)) return null; } - myNotInstanceofValues.add(dfaType); - return true; + HashSet<DfaPsiType> newNotInstanceof = ContainerUtil.newHashSet(myNotInstanceofValues); + newNotInstanceof.add(dfaType.getDfaType()); + return createCopy(myInstanceofValues, newNotInstanceof, myNullability); } public int hashCode() { - return myInstanceofValues.hashCode() + myNotInstanceofValues.hashCode(); + return (myInstanceofValues.hashCode() * 31 + myNotInstanceofValues.hashCode()) * 31 + myNullability.hashCode(); } public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof DfaVariableState)) return false; DfaVariableState aState = (DfaVariableState) obj; - return myInstanceofValues.equals(aState.myInstanceofValues) && - myNotInstanceofValues.equals(aState.myNotInstanceofValues) && - myNullability == aState.myNullability; + return myNullability == aState.myNullability && + myInstanceofValues.equals(aState.myInstanceofValues) && + myNotInstanceofValues.equals(aState.myNotInstanceofValues); } - @Override - protected DfaVariableState clone() { - return new DfaVariableState(this); + protected DfaVariableState createCopy(Set<DfaPsiType> instanceofValues, Set<DfaPsiType> notInstanceofValues, Nullness nullability) { + return new DfaVariableState(instanceofValues, notInstanceofValues, nullability); } public String toString() { @NonNls StringBuilder buf = new StringBuilder(); + buf.append(myNullability); if (!myInstanceofValues.isEmpty()) { - buf.append("instanceof "); - for (Iterator<DfaTypeValue> iterator = myInstanceofValues.iterator(); iterator.hasNext();) { - DfaTypeValue dfaTypeValue = iterator.next(); - buf.append("{").append(dfaTypeValue).append("}"); - if (iterator.hasNext()) buf.append(", "); - } + buf.append(" instanceof ").append(StringUtil.join(myInstanceofValues, ",")); } if (!myNotInstanceofValues.isEmpty()) { - buf.append("not instanceof "); - for (Iterator<DfaTypeValue> iterator = myNotInstanceofValues.iterator(); iterator.hasNext();) { - DfaTypeValue dfaTypeValue = iterator.next(); - buf.append("{").append(dfaTypeValue).append("}"); - if (iterator.hasNext()) buf.append(", "); - } + buf.append(" not instanceof ").append(StringUtil.join(myNotInstanceofValues, ",")); } - buf.append(myNullability); return buf.toString(); } + public Nullness getNullability() { + return myNullability; + } + public boolean isNotNull() { return myNullability == Nullness.NOT_NULL; } - public void setNullable(final boolean nullable) { - if (myNullability != Nullness.NOT_NULL) { - myNullability = nullable ? Nullness.NULLABLE : Nullness.UNKNOWN; - } + DfaVariableState withNullability(@NotNull Nullness nullness) { + return myNullability == nullness ? this : createCopy(myInstanceofValues, myNotInstanceofValues, nullness); + } + + public DfaVariableState withNullable(final boolean nullable) { + return myNullability != Nullness.NOT_NULL ? withNullability(nullable ? Nullness.NULLABLE : Nullness.UNKNOWN) : this; } - public void setValue(DfaValue value) { + public DfaVariableState withValue(DfaValue value) { + return this; } @Nullable diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java new file mode 100644 index 000000000000..c6fc62170ef2 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/EqClass.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.dataFlow; + +import com.intellij.codeInspection.dataFlow.value.DfaConstValue; +import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.TIntProcedure; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author peter + */ +class EqClass extends SortedIntSet { + private final DfaValueFactory myFactory; + + EqClass(DfaValueFactory factory) { + myFactory = factory; + } + + EqClass(EqClass toCopy) { + super(toCopy.toNativeArray()); + myFactory = toCopy.myFactory; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (int i = 0; i < size(); i++) { + if (i > 0) buf.append(", "); + int value = get(i); + DfaValue dfaValue = myFactory.getValue(value); + buf.append(dfaValue); + } + buf.append(")"); + return buf.toString(); + } + + List<DfaVariableValue> getVariables() { + List<DfaVariableValue> vars = ContainerUtil.newArrayList(); + for (DfaValue value : getMemberValues()) { + value = DfaMemoryStateImpl.unwrap(value); + if (value instanceof DfaVariableValue) { + vars.add((DfaVariableValue)value); + } + } + return vars; + } + + List<DfaValue> getMemberValues() { + final List<DfaValue> result = new ArrayList<DfaValue>(size()); + forEach(new TIntProcedure() { + @Override + public boolean execute(int c1) { + DfaValue value = myFactory.getValue(c1); + result.add(value); + return true; + } + }); + return result; + } + + @Nullable + DfaConstValue findConstant(boolean wrapped) { + for (DfaValue value : getMemberValues()) { + if (wrapped) { + value = DfaMemoryStateImpl.unwrap(value); + } + if (value instanceof DfaConstValue) { + return (DfaConstValue)value; + } + } + return null; + } + + @Nullable + private static DfaConstValue asConstantValue(DfaValue value) { + value = DfaMemoryStateImpl.unwrap(value); + return value instanceof DfaConstValue ? (DfaConstValue)value : null; + } + + boolean containsConstantsOnly() { + for (int i = 0; i < size(); i++) { + if (asConstantValue(myFactory.getValue(get(i))) == null) { + return false; + } + } + + return true; + } + +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java new file mode 100644 index 000000000000..8155149ed1ec --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java @@ -0,0 +1,14 @@ +package com.intellij.codeInspection.dataFlow; + +/** + * @author peter + */ +public enum NullabilityProblem { + callNPE, + fieldAccessNPE, + unboxingNullable, + assigningToNotNull, + nullableReturn, + passingNullableToNotNullParameter, + passingNullableArgumentToNonAnnotatedParameter, +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java index b7fc6c7dcfc8..2d6ee1b71232 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardDataFlowRunner.java @@ -27,36 +27,23 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInspection.dataFlow.instructions.InstanceofInstruction; import com.intellij.codeInspection.dataFlow.instructions.Instruction; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; -import com.intellij.psi.*; -import gnu.trove.THashSet; +import com.intellij.psi.CommonClassNames; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiType; import org.jetbrains.annotations.NotNull; import java.util.HashSet; import java.util.Set; public class StandardDataFlowRunner extends DataFlowRunner { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowRunner"); - - private final Set<Instruction> myNPEInstructions = new HashSet<Instruction>(); private final Set<Instruction> myCCEInstructions = new HashSet<Instruction>(); - private final Set<PsiExpression> myNullableArguments = new HashSet<PsiExpression>(); - private final Set<PsiExpression> myNullableArgumentsPassedToNonAnnotatedParam = new HashSet<PsiExpression>(); - private final Set<PsiExpression> myNullableAssignments = new HashSet<PsiExpression>(); - private final Set<PsiReturnStatement> myNullableReturns = new HashSet<PsiReturnStatement>(); - private final boolean mySuggestNullableAnnotations; private boolean myInNullableMethod = false; private boolean myInNotNullMethod = false; private boolean myIsInMethod = false; - private final Set<PsiExpression> myUnboxedNullables = new THashSet<PsiExpression>(); - - public StandardDataFlowRunner(boolean suggestNullableAnnotations) { - mySuggestNullableAnnotations = suggestNullableAnnotations; - } - @Override protected void prepareAnalysis(@NotNull PsiElement psiBlock, Iterable<DfaMemoryState> initialStates) { myIsInMethod = psiBlock.getParent() instanceof PsiMethod; @@ -68,17 +55,7 @@ public class StandardDataFlowRunner extends DataFlowRunner { myInNotNullMethod = NullableNotNullManager.isNotNull(method); } - myNPEInstructions.clear(); myCCEInstructions.clear(); - myNullableArguments.clear(); - myNullableArgumentsPassedToNonAnnotatedParam.clear(); - myNullableAssignments.clear(); - myNullableReturns.clear(); - myUnboxedNullables.clear(); - } - - public void onInstructionProducesNPE(Instruction instruction) { - myNPEInstructions.add(instruction); } public void onInstructionProducesCCE(Instruction instruction) { @@ -89,74 +66,24 @@ public class StandardDataFlowRunner extends DataFlowRunner { return myCCEInstructions; } - @NotNull public Set<Instruction> getNPEInstructions() { - return myNPEInstructions; - } - - @NotNull public Set<PsiReturnStatement> getNullableReturns() { - return myNullableReturns; - } - public boolean isInNotNullMethod() { return myInNotNullMethod; } - @NotNull public Set<PsiExpression> getNullableArguments() { - return myNullableArguments; - } - - public Set<PsiExpression> getNullableArgumentsPassedToNonAnnotatedParam() { - return myNullableArgumentsPassedToNonAnnotatedParam; - } - - @NotNull public Set<PsiExpression> getNullableAssignments() { - return myNullableAssignments; + public boolean isInNullableMethod() { + return myInNullableMethod; } - @NotNull public Set<PsiExpression> getUnboxedNullables() { - return myUnboxedNullables; - } - - public void onUnboxingNullable(@NotNull PsiExpression expression) { - LOG.assertTrue(expression.isValid()); - if (expression.isPhysical()) { - myUnboxedNullables.add(expression); - } - } - - public void onPassingNullParameter(PsiExpression expr) { - myNullableArguments.add(expr); - } - - public void onPassingNullParameterToNonAnnotated(PsiExpression expr) { - if (mySuggestNullableAnnotations) { - myNullableArgumentsPassedToNonAnnotatedParam.add(expr); - } - } - - public void onAssigningToNotNullableVariable(final PsiExpression expr) { - myNullableAssignments.add(expr); - } - - public void onNullableReturn(final PsiReturnStatement statement) { - if (myInNullableMethod || !myIsInMethod) return; - if (myInNotNullMethod || mySuggestNullableAnnotations) { - myNullableReturns.add(statement); - } + public boolean isInMethod() { + return myIsInMethod; } public boolean problemsDetected(StandardInstructionVisitor visitor) { final Pair<Set<Instruction>, Set<Instruction>> constConditions = getConstConditionalExpressions(); return !constConditions.getFirst().isEmpty() || !constConditions.getSecond().isEmpty() - || !myNPEInstructions.isEmpty() || !myCCEInstructions.isEmpty() - || !getRedundantInstanceofs(this, visitor).isEmpty() - || !myNullableArguments.isEmpty() - || !myNullableArgumentsPassedToNonAnnotatedParam.isEmpty() - || !myNullableAssignments.isEmpty() - || !myNullableReturns.isEmpty() - || !myUnboxedNullables.isEmpty(); + || !getRedundantInstanceofs(this, visitor).isEmpty(); } @NotNull public static Set<Instruction> getRedundantInstanceofs(final DataFlowRunner runner, StandardInstructionVisitor visitor) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 3770e49600db..90a64901c424 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -115,17 +115,15 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (dfaDest instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue) dfaDest; - final PsiModifierListOwner psiVariable = var.getPsiVariable(); - if (DfaPsiUtil.getElementNullability(var.getVariableType(), psiVariable) == Nullness.NOT_NULL) { - if (!memState.checkNotNullable(dfaSource)) { - onAssigningToNotNullableVariable(instruction); - } + if (var.getInherentNullability() == Nullness.NOT_NULL) { + checkNotNullable(memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression()); } - if (!(psiVariable instanceof PsiField) || !psiVariable.hasModifierProperty(PsiModifier.VOLATILE)) { + final PsiModifierListOwner psi = var.getPsiVariable(); + if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) { memState.setVarValue(var, dfaSource); } - } else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue)dfaDest).isNotNull() && !memState.checkNotNullable(dfaSource)) { - onAssigningToNotNullableVariable(instruction); + } else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue)dfaDest).isNotNull()) { + checkNotNullable(memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression()); } memState.push(dfaDest); @@ -133,27 +131,19 @@ public class StandardInstructionVisitor extends InstructionVisitor { return nextInstruction(instruction, runner, memState); } - protected void onAssigningToNotNullableVariable(AssignInstruction instruction) {} - @Override public DfaInstructionState[] visitCheckReturnValue(CheckReturnValueInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaValue retValue = memState.pop(); - if (!memState.checkNotNullable(retValue)) { - onNullableReturn(instruction); - } + checkNotNullable(memState, retValue, NullabilityProblem.nullableReturn, instruction.getReturn()); return nextInstruction(instruction, runner, memState); } - protected void onNullableReturn(CheckReturnValueInstruction instruction) {} - @Override public DfaInstructionState[] visitFieldReference(FieldReferenceInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaValue qualifier = memState.pop(); - if (!memState.checkNotNullable(qualifier)) { - onInstructionProducesNPE(instruction); - + if (!checkNotNullable(memState, qualifier, NullabilityProblem.fieldAccessNPE, instruction.getElementToAssert())) { if (qualifier instanceof DfaVariableValue) { memState.setVarValue((DfaVariableValue)qualifier, runner.getFactory() .createTypeValue(((DfaVariableValue)qualifier).getVariableType(), Nullness.NOT_NULL)); @@ -194,7 +184,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { final DfaValueFactory factory = runner.getFactory(); DfaValue dfaExpr = factory.createValue(instruction.getCasted()); if (dfaExpr != null) { - DfaTypeValue dfaType = factory.getTypeFactory().createTypeValue(instruction.getCastTo()); + DfaTypeValue dfaType = (DfaTypeValue)factory.createTypeValue(instruction.getCastTo(), Nullness.UNKNOWN); DfaRelationValue dfaInstanceof = factory.getRelationFactory().createRelation(dfaExpr, dfaType, JavaTokenType.INSTANCEOF_KEYWORD, false); if (dfaInstanceof != null && !memState.applyInstanceofOrNull(dfaInstanceof)) { onInstructionProducesCCE(instruction); @@ -218,23 +208,24 @@ public class StandardInstructionVisitor extends InstructionVisitor { final DfaValue arg = memState.pop(); PsiExpression expr = args[(args.length - i - 1)]; if (map.get(expr) == Nullness.NOT_NULL) { - if (!memState.checkNotNullable(arg)) { - onPassingNullParameter(expr); + if (!checkNotNullable(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, expr)) { if (arg instanceof DfaVariableValue) { memState.setVarValue((DfaVariableValue)arg, runner.getFactory() .createTypeValue(((DfaVariableValue)arg).getVariableType(), Nullness.NOT_NULL)); } } } - else if (map.get(expr) == Nullness.UNKNOWN && !memState.checkNotNullable(arg)) { - onPassingNullParameterToNonAnnotated(runner, expr); + else if (map.get(expr) == Nullness.UNKNOWN) { + checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr); } } @NotNull final DfaValue qualifier = memState.pop(); try { - if (!memState.checkNotNullable(qualifier)) { - onInstructionProducesNPE(instruction); + boolean unboxing = instruction.getMethodType() == MethodCallInstruction.MethodType.UNBOXING; + NullabilityProblem problem = unboxing ? NullabilityProblem.unboxingNullable : NullabilityProblem.callNPE; + PsiExpression anchor = unboxing ? instruction.getContext() : instruction.getCallExpression(); + if (!checkNotNullable(memState, qualifier, problem, anchor)) { if (qualifier instanceof DfaVariableValue) { memState.setVarValue((DfaVariableValue)qualifier, runner.getFactory().createTypeValue( ((DfaVariableValue)qualifier).getVariableType(), Nullness.NOT_NULL)); @@ -296,11 +287,11 @@ public class StandardInstructionVisitor extends InstructionVisitor { return TypeConversionUtil.computeCastTo(o, PsiType.LONG); } - - protected void onInstructionProducesNPE(Instruction instruction) {} - - protected void onPassingNullParameter(PsiExpression arg) {} - protected void onPassingNullParameterToNonAnnotated(DataFlowRunner runner, PsiExpression arg) {} + protected boolean checkNotNullable(DfaMemoryState state, + DfaValue value, NullabilityProblem problem, + PsiElement anchor) { + return state.checkNotNullable(value); + } @Override public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { @@ -351,11 +342,10 @@ public class StandardInstructionVisitor extends InstructionVisitor { return null; } - if (isViaMethods(dfaLeft) || isViaMethods(dfaRight)) { - skipConstantConditionReporting(instruction.getPsiAnchor()); - } myCanBeNullInInstanceof.add(instruction); + boolean specialContractTreatment = isUnknownComparisonWithNullInContract(instruction, dfaLeft, dfaRight, factory, memState); + ArrayList<DfaInstructionState> states = new ArrayList<DfaInstructionState>(); final DfaMemoryState trueCopy = memState.createCopy(); @@ -363,6 +353,9 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (!dfaRelation.isNegated()) { checkOneOperandNotNull(dfaRight, dfaLeft, factory, trueCopy); } + if (specialContractTreatment && !dfaRelation.isNegated()) { + trueCopy.markEphemeral(); + } trueCopy.push(factory.getConstFactory().getTrue()); instruction.setTrueReachable(); states.add(new DfaInstructionState(next, trueCopy)); @@ -374,6 +367,9 @@ public class StandardInstructionVisitor extends InstructionVisitor { if (dfaRelation.isNegated()) { checkOneOperandNotNull(dfaRight, dfaLeft, factory, falseCopy); } + if (specialContractTreatment && dfaRelation.isNegated()) { + falseCopy.markEphemeral(); + } falseCopy.push(factory.getConstFactory().getFalse()); instruction.setFalseReachable(); states.add(new DfaInstructionState(next, falseCopy)); @@ -385,12 +381,25 @@ public class StandardInstructionVisitor extends InstructionVisitor { return states.toArray(new DfaInstructionState[states.size()]); } - public void skipConstantConditionReporting(@Nullable PsiElement anchor) { - ContainerUtil.addIfNotNull(myNotToReportReachability, anchor); + private static boolean isUnknownComparisonWithNullInContract(BinopInstruction instruction, + DfaValue dfaLeft, + DfaValue dfaRight, + DfaValueFactory factory, + DfaMemoryState memoryState) { + if (instruction.getPsiAnchor() != null || dfaRight != factory.getConstFactory().getNull()) { + return false; + } + if (dfaLeft instanceof DfaVariableValue) { + return ((DfaMemoryStateImpl)memoryState).getVariableState((DfaVariableValue)dfaLeft).getNullability() == Nullness.UNKNOWN; + } + if (dfaLeft instanceof DfaTypeValue) { + return ((DfaTypeValue)dfaLeft).getNullness() == Nullness.UNKNOWN; + } + return false; } - private static boolean isViaMethods(DfaValue dfa) { - return dfa instanceof DfaVariableValue && ((DfaVariableValue)dfa).isViaMethods(); + public void skipConstantConditionReporting(@Nullable PsiElement anchor) { + ContainerUtil.addIfNotNull(myNotToReportReachability, anchor); } private void handleInstanceof(InstanceofInstruction instruction, DfaValue dfaRight, DfaValue dfaLeft) { @@ -399,7 +408,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { myCanBeNullInInstanceof.add(instruction); } - if (((DfaTypeValue)dfaRight).getType().isAssignableFrom(((DfaTypeValue)dfaLeft).getType())) { + if (((DfaTypeValue)dfaRight).getDfaType().isAssignableFrom(((DfaTypeValue)dfaLeft).getDfaType())) { return; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java new file mode 100644 index 000000000000..e886dde637a0 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java @@ -0,0 +1,217 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.dataFlow; + +import com.intellij.codeInspection.dataFlow.value.DfaConstValue; +import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.UnorderedPair; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author peter + */ +class StateMerger { + private final List<DfaMemoryStateImpl> myStates; + private final MultiMap<UnorderedPair<DfaValue>,DfaMemoryStateImpl> myStatesByEq = new MultiMap<UnorderedPair<DfaValue>, DfaMemoryStateImpl>(); + private final Map<DfaMemoryStateImpl, Map<DfaVariableValue, DfaConstValue>> myVarValues = ContainerUtil.newIdentityHashMap(); + + public StateMerger(List<DfaMemoryStateImpl> states) { + myStates = states; + for (DfaMemoryStateImpl state : myStates) { + ProgressManager.checkCanceled(); + + Map<DfaVariableValue,DfaConstValue> varValues = ContainerUtil.newHashMap(); + for (UnorderedPair<DfaValue> pair : getEqPairs(state)) { + myStatesByEq.putValue(pair, state); + if (pair.first instanceof DfaVariableValue && pair.second instanceof DfaConstValue) { + varValues.put((DfaVariableValue)pair.first, (DfaConstValue)pair.second); + } + } + myVarValues.put(state, varValues); + } + } + + @Nullable + public List<DfaMemoryStateImpl> merge() { + for (final DfaMemoryStateImpl state : myStates) { + ProgressManager.checkCanceled(); + MultiMap<DfaVariableValue, DfaValue> distincts = getDistinctsMap(state); + for (DfaVariableValue var : distincts.keySet()) { + Map<DfaValue, Collection<DfaMemoryStateImpl>> statesByValue = getCompatibleStatesByValue(state, var, distincts); + if (statesByValue == null) { + continue; + } + + DfaMemoryStateImpl copy = copyWithoutVar(state, var); + + final Set<DfaMemoryStateImpl> complementaryStates = findComplementaryStates(var, statesByValue, copy); + if (complementaryStates == null) { + continue; + } + + complementaryStates.add(state); + copy = copy.createCopy(); + for (DfaMemoryStateImpl removedState : complementaryStates) { + for (DfaVariableValue unknownVar : removedState.getUnknownVariables()) { + copy.doFlush(unknownVar, true); + } + if (removedState.isNull(var)) { + copy.setVariableState(var, copy.getVariableState(var).withNullability(Nullness.NULLABLE)); + } + } + + List<DfaMemoryStateImpl> result = ContainerUtil.newArrayList(); + result.add(copy); + result.addAll(ContainerUtil.filter(myStates, new Condition<DfaMemoryStateImpl>() { + @Override + public boolean value(DfaMemoryStateImpl state) { + return !complementaryStates.contains(state); + } + })); + return result; + } + + } + return null; + } + + private Map<Pair<DfaMemoryStateImpl, DfaVariableValue>, DfaMemoryStateImpl> myCopyCache = ContainerUtil.newHashMap(); + private DfaMemoryStateImpl copyWithoutVar(DfaMemoryStateImpl state, DfaVariableValue var) { + Pair<DfaMemoryStateImpl, DfaVariableValue> key = Pair.create(state, var); + DfaMemoryStateImpl copy = myCopyCache.get(key); + if (copy == null) { + copy = state.createCopy(); + copy.flushVariable(var); + myCopyCache.put(key, copy); + } + return copy; + } + + @Nullable + private Set<DfaMemoryStateImpl> findComplementaryStates(DfaVariableValue var, + Map<DfaValue, Collection<DfaMemoryStateImpl>> statesByValue, + DfaMemoryStateImpl mainCopy) { + Set<DfaMemoryStateImpl> removedStates = ContainerUtil.newTroveSet(ContainerUtil.<DfaMemoryStateImpl>identityStrategy()); + + eachValue: + for (DfaValue value : statesByValue.keySet()) { + for (DfaMemoryStateImpl originalState : statesByValue.get(value)) { + if (mainCopy.equalsByRelations(copyWithoutVar(originalState, var))) { + removedStates.add(originalState); + continue eachValue; + } + } + return null; + } + return removedStates; + } + + @Nullable + private Map<DfaValue, Collection<DfaMemoryStateImpl>> getCompatibleStatesByValue(final DfaMemoryStateImpl state, + final DfaVariableValue var, + MultiMap<DfaVariableValue, DfaValue> distincts) { + Map<DfaValue, Collection<DfaMemoryStateImpl>> statesByValue = ContainerUtil.newHashMap(); + for (DfaValue value : distincts.get(var)) { + List<DfaMemoryStateImpl> compatible = ContainerUtil.filter(myStatesByEq.get(createPair(var, value)), new Condition<DfaMemoryStateImpl>() { + @Override + public boolean value(DfaMemoryStateImpl state2) { + return areCompatible(state, state2, var); + } + }); + if (compatible.isEmpty()) { + return null; + } + statesByValue.put(value, compatible); + } + return statesByValue; + } + + private boolean areCompatible(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue differentVar) { + if (!state1.equalsSuperficially(state2)) { + return false; + } + Map<DfaVariableValue, DfaConstValue> varValues1 = myVarValues.get(state1); + Map<DfaVariableValue, DfaConstValue> varValues2 = myVarValues.get(state2); + + for (DfaVariableValue var : varValues1.keySet()) { + if (var != differentVar && varValues1.get(var) != varValues2.get(var)) { + return false; + } + } + for (DfaVariableValue var : varValues2.keySet()) { + if (var != differentVar && !varValues1.containsKey(var)) { + return false; + } + } + return true; + } + + private static MultiMap<DfaVariableValue, DfaValue> getDistinctsMap(DfaMemoryStateImpl state) { + MultiMap<DfaVariableValue, DfaValue> distincts = new MultiMap<DfaVariableValue, DfaValue>(); + for (UnorderedPair<EqClass> classPair : state.getDistinctClassPairs()) { + for (DfaValue value1 : classPair.first.getMemberValues()) { + value1 = DfaMemoryStateImpl.unwrap(value1); + for (DfaValue value2 : classPair.second.getMemberValues()) { + value2 = DfaMemoryStateImpl.unwrap(value2); + if (value1 instanceof DfaVariableValue) { + if (value2 instanceof DfaVariableValue || value2 instanceof DfaConstValue) { + distincts.putValue((DfaVariableValue)value1, value2); + } + } + if (value2 instanceof DfaVariableValue) { + if (value1 instanceof DfaVariableValue || value1 instanceof DfaConstValue) { + distincts.putValue((DfaVariableValue)value2, value1); + } + } + } + } + } + return distincts; + } + + private static List<UnorderedPair<DfaValue>> getEqPairs(DfaMemoryStateImpl state) { + Set<UnorderedPair<DfaValue>> eqPairs = ContainerUtil.newHashSet(); + for (EqClass eqClass : state.getNonTrivialEqClasses()) { + DfaConstValue constant = eqClass.findConstant(true); + List<DfaVariableValue> vars = eqClass.getVariables(); + for (int i = 0; i < vars.size(); i++) { + DfaVariableValue var = vars.get(i); + if (constant != null) { + eqPairs.add(createPair(var, constant)); + } + for (int j = i + 1; j < vars.size(); j++) { + eqPairs.add(createPair(var, vars.get(j))); + } + } + } + return ContainerUtil.newArrayList(eqPairs); + } + + private static UnorderedPair<DfaValue> createPair(DfaVariableValue var, DfaValue val) { + return new UnorderedPair<DfaValue>(var, val); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java index e4631d66c79e..87aa89759798 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ValuableDataFlowRunner.java @@ -16,10 +16,14 @@ package com.intellij.codeInspection.dataFlow; +import com.intellij.codeInspection.dataFlow.value.DfaPsiType; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.psi.PsiExpression; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; /** * @author Gregory.Shrago @@ -36,9 +40,13 @@ public class ValuableDataFlowRunner extends DataFlowRunner { super(factory); } + MyDfaMemoryState(DfaMemoryStateImpl toCopy) { + super(toCopy); + } + @Override - protected DfaMemoryStateImpl createNew() { - return new MyDfaMemoryState(getFactory()); + public DfaMemoryStateImpl createCopy() { + return new MyDfaMemoryState(this); } @Override @@ -49,21 +57,37 @@ public class ValuableDataFlowRunner extends DataFlowRunner { } static class ValuableDfaVariableState extends DfaVariableState { - DfaValue myValue; - PsiExpression myExpression; + final DfaValue myValue; + final PsiExpression myExpression; private ValuableDfaVariableState(final DfaVariableValue psiVariable) { super(psiVariable); + myValue = null; + myExpression = null; + } + + private ValuableDfaVariableState(Set<DfaPsiType> instanceofValues, + Set<DfaPsiType> notInstanceofValues, + Nullness nullability, DfaValue value, PsiExpression expression) { + super(instanceofValues, notInstanceofValues, nullability); + myValue = value; + myExpression = expression; } - protected ValuableDfaVariableState(final ValuableDfaVariableState state) { - super(state); - myExpression = state.myExpression; + @Override + protected DfaVariableState createCopy(Set<DfaPsiType> instanceofValues, Set<DfaPsiType> notInstanceofValues, Nullness nullability) { + return new ValuableDfaVariableState(instanceofValues, notInstanceofValues, nullability, myValue, myExpression); } @Override - public void setValue(final DfaValue value) { - myValue = value; + public DfaVariableState withValue(@Nullable final DfaValue value) { + if (value == myValue) return this; + return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, value, myExpression); + } + + public ValuableDfaVariableState withExpression(@Nullable final PsiExpression expression) { + if (expression == myExpression) return this; + return new ValuableDfaVariableState(myInstanceofValues, myNotInstanceofValues, myNullability, myValue, expression); } @Override @@ -72,8 +96,25 @@ public class ValuableDataFlowRunner extends DataFlowRunner { } @Override - protected ValuableDfaVariableState clone() { - return new ValuableDfaVariableState(this); + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ValuableDfaVariableState)) return false; + if (!super.equals(o)) return false; + + ValuableDfaVariableState state = (ValuableDfaVariableState)o; + + if (myExpression != null ? !myExpression.equals(state.myExpression) : state.myExpression != null) return false; + if (myValue != null ? !myValue.equals(state.myValue) : state.myValue != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (myValue != null ? myValue.hashCode() : 0); + result = 31 * result + (myExpression != null ? myExpression.hashCode() : 0); + return result; } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java index dd2514c41f8e..6e64a6b86416 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BinopInstruction.java @@ -33,6 +33,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static com.intellij.psi.JavaTokenType.*; @@ -41,11 +42,10 @@ public class BinopInstruction extends BranchingInstruction { private final IElementType myOperationSign; private final Project myProject; - public BinopInstruction(IElementType opSign, PsiElement psiAnchor, @NotNull Project project) { + public BinopInstruction(IElementType opSign, @Nullable PsiElement psiAnchor, @NotNull Project project) { + super(psiAnchor); myProject = project; myOperationSign = ourSignificantOperations.contains(opSign) ? opSign : null; - - setPsiAnchor(psiAnchor); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BranchingInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BranchingInstruction.java index d1524fc42092..0f83d84afd28 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BranchingInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/BranchingInstruction.java @@ -27,17 +27,19 @@ package com.intellij.codeInspection.dataFlow.instructions; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiLiteralExpression; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; public abstract class BranchingInstruction extends Instruction { private boolean myIsTrueReachable; private boolean myIsFalseReachable; - private boolean isConstTrue; - private PsiElement myExpression; + private final boolean isConstTrue; + private final PsiElement myExpression; - protected BranchingInstruction() { + protected BranchingInstruction(@Nullable PsiElement psiAnchor) { myIsTrueReachable = false; myIsFalseReachable = false; - setPsiAnchor(null); + myExpression = psiAnchor; + isConstTrue = psiAnchor != null && isBoolConst(psiAnchor); } public boolean isTrueReachable() { @@ -70,8 +72,4 @@ public abstract class BranchingInstruction extends Instruction { return "true".equals(text) || "false".equals(text); } - protected void setPsiAnchor(PsiElement psiAnchor) { - myExpression = psiAnchor; - isConstTrue = psiAnchor != null && isBoolConst(psiAnchor); - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java index 2b93e95045cb..212a02f361ad 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java @@ -33,9 +33,9 @@ public class ConditionalGotoInstruction extends BranchingInstruction { private final boolean myIsNegated; public ConditionalGotoInstruction(ControlFlow.ControlFlowOffset myOffset, boolean isNegated, PsiElement psiAnchor) { + super(psiAnchor); this.myOffset = myOffset; myIsNegated = isNegated; - setPsiAnchor(psiAnchor); } public boolean isNegated() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GosubInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GosubInstruction.java index 9d2a5944c138..dc1fd6fc6f74 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GosubInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GosubInstruction.java @@ -27,15 +27,19 @@ public class GosubInstruction extends Instruction { mySubprogramOffset = subprogramOffset; } + public int getSubprogramOffset() { + return mySubprogramOffset.getInstructionOffset(); + } + @Override public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { final int returnIndex = getIndex() + 1; stateBefore.pushOffset(returnIndex); - Instruction nextInstruction = runner.getInstruction(mySubprogramOffset.getInstructionOffset()); + Instruction nextInstruction = runner.getInstruction(getSubprogramOffset()); return new DfaInstructionState[] {new DfaInstructionState(nextInstruction, stateBefore)}; } public String toString() { - return "GOSUB: " + mySubprogramOffset.getInstructionOffset(); + return "GOSUB: " + getSubprogramOffset(); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java index 328609095456..c4400f771fcc 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java @@ -34,14 +34,18 @@ public class GotoInstruction extends Instruction { this.myOffset = myOffset; } + public int getOffset() { + return myOffset.getInstructionOffset(); + } + @Override public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { - Instruction nextInstruction = runner.getInstruction(myOffset.getInstructionOffset()); + Instruction nextInstruction = runner.getInstruction(getOffset()); return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, stateBefore)}; } public String toString() { - return "GOTO: " + myOffset.getInstructionOffset(); + return "GOTO: " + getOffset(); } public void setOffset(final int offset) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/Instruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/Instruction.java index 5514268233cd..096188ec1e8e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/Instruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/Instruction.java @@ -28,18 +28,9 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner; import com.intellij.codeInspection.dataFlow.DfaInstructionState; import com.intellij.codeInspection.dataFlow.DfaMemoryState; import com.intellij.codeInspection.dataFlow.InstructionVisitor; -import com.intellij.openapi.progress.ProgressManager; - -import java.util.ArrayList; -import java.util.List; public abstract class Instruction { private int myIndex; - private final List<DfaMemoryState> myProcessedStates; - - protected Instruction() { - myProcessedStates = new ArrayList<DfaMemoryState>(); - } protected final DfaInstructionState[] nextInstruction(DataFlowRunner runner, DfaMemoryState stateBefore) { return new DfaInstructionState[] {new DfaInstructionState(runner.getInstruction(getIndex() + 1), stateBefore)}; @@ -47,23 +38,6 @@ public abstract class Instruction { public abstract DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor); - public boolean isMemoryStateProcessed(DfaMemoryState dfaMemState) { - for (DfaMemoryState state : myProcessedStates) { - ProgressManager.checkCanceled(); - if (dfaMemState.equals(state)) { - return true; - } - } - - return false; - } - - public boolean setMemoryStateProcessed(DfaMemoryState dfaMemState) { - if (myProcessedStates.size() > DataFlowRunner.MAX_STATES_PER_BRANCH) return false; - myProcessedStates.add(dfaMemState); - return true; - } - public void setIndex(int index) { myIndex = index; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaPsiType.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaPsiType.java new file mode 100644 index 000000000000..f58e44fc83e4 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaPsiType.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.dataFlow.value; + +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiType; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * @author peter + */ +public class DfaPsiType { + private final PsiType myPsiType; + private final Map<Pair<DfaPsiType, DfaPsiType>, Boolean> myAssignableCache; + private final Map<Pair<DfaPsiType, DfaPsiType>, Boolean> myConvertibleCache; + + DfaPsiType(@NotNull PsiType psiType, Map<Pair<DfaPsiType, DfaPsiType>, Boolean> assignableCache, Map<Pair<DfaPsiType, DfaPsiType>, Boolean> convertibleCache) { + myPsiType = psiType; + myAssignableCache = assignableCache; + myConvertibleCache = convertibleCache; + } + + @NotNull + public PsiType getPsiType() { + return myPsiType; + } + + public boolean isAssignableFrom(DfaPsiType other) { + if (other == this) return true; + Pair<DfaPsiType, DfaPsiType> key = Pair.create(this, other); + Boolean result = myAssignableCache.get(key); + if (result == null) { + myAssignableCache.put(key, result = myPsiType.isAssignableFrom(other.myPsiType)); + } + return result; + } + + public boolean isConvertibleFrom(DfaPsiType other) { + if (other == this) return true; + Pair<DfaPsiType, DfaPsiType> key = Pair.create(this, other); + Boolean result = myConvertibleCache.get(key); + if (result == null) { + myConvertibleCache.put(key, result = myPsiType.isConvertibleFrom(other.myPsiType)); + } + return result; + } + + @Override + public String toString() { + return myPsiType.getPresentableText(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java index f7b648762365..193d3426ea29 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaTypeValue.java @@ -25,72 +25,51 @@ package com.intellij.codeInspection.dataFlow.value; import com.intellij.codeInspection.dataFlow.Nullness; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiKeyword; -import com.intellij.psi.PsiType; -import com.intellij.psi.util.TypeConversionUtil; -import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; +import java.util.Map; public class DfaTypeValue extends DfaValue { public static class Factory { - private final DfaTypeValue mySharedInstance; - private final HashMap<String,ArrayList<DfaTypeValue>> myStringToObject; + private final Map<DfaPsiType,ArrayList<DfaTypeValue>> myCache = ContainerUtil.newHashMap(); private final DfaValueFactory myFactory; Factory(DfaValueFactory factory) { myFactory = factory; - mySharedInstance = new DfaTypeValue(factory); - myStringToObject = new HashMap<String, ArrayList<DfaTypeValue>>(); } @NotNull - public DfaTypeValue createTypeValue(@NotNull PsiType type, @NotNull Nullness nullable) { - type = TypeConversionUtil.erasure(type); - mySharedInstance.myType = type; - mySharedInstance.myCanonicalText = StringUtil.notNullize(type.getCanonicalText(), PsiKeyword.NULL); - mySharedInstance.myNullness = nullable; - - String id = mySharedInstance.toString(); - ArrayList<DfaTypeValue> conditions = myStringToObject.get(id); + public DfaTypeValue createTypeValue(@NotNull DfaPsiType type, @NotNull Nullness nullness) { + ArrayList<DfaTypeValue> conditions = myCache.get(type); if (conditions == null) { conditions = new ArrayList<DfaTypeValue>(); - myStringToObject.put(id, conditions); + myCache.put(type, conditions); } else { for (DfaTypeValue aType : conditions) { - if (aType.hardEquals(mySharedInstance)) return aType; + if (aType.myNullness == nullness) return aType; } } - DfaTypeValue result = new DfaTypeValue(type, nullable, myFactory, mySharedInstance.myCanonicalText); + DfaTypeValue result = new DfaTypeValue(type, nullness, myFactory); conditions.add(result); - return result; + return new DfaTypeValue(type, nullness, myFactory); } - public DfaTypeValue createTypeValue(@NotNull PsiType type) { - return createTypeValue(type, Nullness.UNKNOWN); - } } - private PsiType myType; - private String myCanonicalText; + private DfaPsiType myType; private Nullness myNullness; - private DfaTypeValue(DfaValueFactory factory) { - super(factory); - } - - private DfaTypeValue(PsiType type, Nullness nullness, DfaValueFactory factory, String canonicalText) { + private DfaTypeValue(DfaPsiType type, Nullness nullness, DfaValueFactory factory) { super(factory); myType = type; myNullness = nullness; - myCanonicalText = canonicalText; } - public PsiType getType() { + public DfaPsiType getDfaType() { return myType; } @@ -102,23 +81,13 @@ public class DfaTypeValue extends DfaValue { return myNullness == Nullness.NOT_NULL; } - @NonNls - public String toString() { - return myCanonicalText + ", nullable=" + myNullness; + public Nullness getNullness() { + return myNullness; } - private boolean hardEquals(DfaTypeValue aType) { - return myCanonicalText.equals(aType.myCanonicalText) && myNullness == aType.myNullness && myType.equals(aType.myType); - } - - public boolean isAssignableFrom(DfaTypeValue dfaType) { - return dfaType != null && myType.isAssignableFrom(dfaType.myType); + @NonNls + public String toString() { + return myType + ", nullable=" + myNullness; } - public boolean isConvertibleFrom(DfaTypeValue dfaType) { - if (dfaType == null) return false; - assert myType.isValid() : "my type invalid"; - assert dfaType.myType.isValid() : " their type invalid"; - return myType.isConvertibleFrom(dfaType.myType); - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java index e19b3ce2125e..c78bfb149d6a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValue.java @@ -21,13 +21,7 @@ public class DfaValue { protected DfaValue(final DfaValueFactory factory) { myFactory = factory; - if (factory == null) { - myID = 0; - } - else { - myID = factory.createID(); - factory.registerValue(this); - } + myID = factory == null ? 0 : factory.registerValue(this); } public int getID() { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index f6b900f9f85d..70cb578bd4fa 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -25,24 +25,26 @@ package com.intellij.codeInspection.dataFlow.value; import com.intellij.codeInspection.dataFlow.Nullness; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.impl.JavaConstantExpressionEvaluator; import com.intellij.psi.util.PsiTreeUtil; -import gnu.trove.TIntObjectHashMap; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class DfaValueFactory { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.value.DfaValueFactory"); +import java.util.List; +import java.util.Map; - private int myLastID; - private final TIntObjectHashMap<DfaValue> myValues; +public class DfaValueFactory { + private final List<DfaValue> myValues = ContainerUtil.newArrayList(); + private final Map<Pair<DfaPsiType, DfaPsiType>, Boolean> myAssignableCache = ContainerUtil.newHashMap(); + private final Map<Pair<DfaPsiType, DfaPsiType>, Boolean> myConvertibleCache = ContainerUtil.newHashMap(); + private final Map<PsiType, DfaPsiType> myDfaTypes = ContainerUtil.newHashMap(); public DfaValueFactory() { - myValues = new TIntObjectHashMap<DfaValue>(); - myLastID = 0; - + myValues.add(null); myVarFactory = new DfaVariableValue.Factory(this); myConstFactory = new DfaConstValue.Factory(this); myBoxedFactory = new DfaBoxedValue.Factory(this); @@ -52,17 +54,20 @@ public class DfaValueFactory { public DfaValue createTypeValue(@Nullable PsiType type, Nullness nullability) { if (type == null) return DfaUnknownValue.getInstance(); - return getTypeFactory().createTypeValue(type, nullability); + return getTypeFactory().createTypeValue(internType(type), nullability); } - int createID() { - myLastID++; - LOG.assertTrue(myLastID >= 0, "Overflow"); - return myLastID; + private DfaPsiType internType(@NotNull PsiType psiType) { + DfaPsiType dfaType = myDfaTypes.get(psiType); + if (dfaType == null) { + myDfaTypes.put(psiType, dfaType = new DfaPsiType(TypeConversionUtil.erasure(psiType), myAssignableCache, myConvertibleCache)); + } + return dfaType; } - void registerValue(DfaValue value) { - myValues.put(value.getID(), value); + int registerValue(DfaValue value) { + myValues.add(value); + return myValues.size() - 1; } public DfaValue getValue(int id) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index e522b8fb2c6f..fa2217a2918b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -30,6 +30,7 @@ import com.intellij.codeInspection.dataFlow.Nullness; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Trinity; import com.intellij.psi.*; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.SmartList; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; @@ -90,7 +91,7 @@ public class DfaVariableValue extends DfaValue { myIsNegated = isNegated; myQualifier = qualifier; myVarType = varType; - myTypeValue = varType == null ? null : myFactory.getTypeFactory().createTypeValue(varType, Nullness.UNKNOWN); + myTypeValue = varType == null ? null : (DfaTypeValue)myFactory.createTypeValue(varType, Nullness.UNKNOWN); } @Nullable @@ -132,7 +133,7 @@ public class DfaVariableValue extends DfaValue { private boolean hardEquals(PsiModifierListOwner psiVar, PsiType varType, boolean negated, DfaVariableValue qualifier) { return psiVar == myVariable && - Comparing.equal(varType, myVarType) && + Comparing.equal(TypeConversionUtil.erasure(varType), TypeConversionUtil.erasure(myVarType)) && negated == myIsNegated && (myQualifier == null ? qualifier == null : myQualifier.hardEquals(qualifier.getPsiVariable(), qualifier.getVariableType(), qualifier.isNegated(), qualifier.getQualifier())); @@ -143,10 +144,6 @@ public class DfaVariableValue extends DfaValue { return myQualifier; } - public boolean isViaMethods() { - return myVariable instanceof PsiMethod || myQualifier != null && myQualifier.isViaMethods(); - } - public Nullness getInherentNullability() { if (myInherentNullability != null) { return myInherentNullability; @@ -190,14 +187,12 @@ public class DfaVariableValue extends DfaValue { return Nullness.UNKNOWN; } - public boolean isLocalVariable() { - return myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter; - } - public boolean isFlushableByCalls() { - if (isLocalVariable()) return false; - if (!myVariable.hasModifierProperty(PsiModifier.FINAL)) return true; - return myQualifier != null && myQualifier.isFlushableByCalls(); + if (myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter) return false; + if (myVariable instanceof PsiVariable && myVariable.hasModifierProperty(PsiModifier.FINAL)) { + return myQualifier != null && myQualifier.isFlushableByCalls(); + } + return true; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaUtilImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaUtilImpl.java index 71fa80a2077f..c46bfb656769 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaUtilImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaUtilImpl.java @@ -223,7 +223,7 @@ public class RefJavaUtilImpl extends RefJavaUtil{ refParent = refParent.getOwner(); } - return (RefClass)refElement; + return refElement instanceof RefClass ? (RefClass)refElement : null; } @Override |