Skip to content

Commit 6805b55

Browse files
committed
Fixed ConcurrentModificationException on compilerArguments
1 parent 22e66a2 commit 6805b55

File tree

3 files changed

+392
-135
lines changed

3 files changed

+392
-135
lines changed

plexus-compilers/plexus-compiler-csharp/src/main/java/org/codehaus/plexus/compiler/csharp/CSharpCompiler.java

Lines changed: 89 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
import java.io.Writer;
2929
import java.nio.file.Paths;
3030
import java.util.ArrayList;
31-
import java.util.Arrays;
31+
import java.util.Collections;
32+
import java.util.HashMap;
3233
import java.util.HashSet;
33-
import java.util.Iterator;
3434
import java.util.List;
3535
import java.util.Map;
3636
import java.util.Set;
@@ -68,26 +68,16 @@ public class CSharpCompiler extends AbstractCompiler {
6868

6969
private static final String[] DEFAULT_INCLUDES = {"**/**"};
7070

71-
private Map<String, String> compilerArguments;
72-
73-
// ----------------------------------------------------------------------
74-
//
75-
// ----------------------------------------------------------------------
76-
7771
public CSharpCompiler() {
7872
super(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES, ".cs", null, null);
7973
}
8074

81-
// ----------------------------------------------------------------------
82-
// Compiler Implementation
83-
// ----------------------------------------------------------------------
84-
8575
@Override
8676
public String getCompilerId() {
8777
return "csharp";
8878
}
8979

90-
public boolean canUpdateTarget(CompilerConfiguration configuration) throws CompilerException {
80+
public boolean canUpdateTarget(CompilerConfiguration configuration) {
9181
return false;
9282
}
9383

@@ -130,37 +120,39 @@ public String[] createCommandLine(CompilerConfiguration config) throws CompilerE
130120
return buildCompilerArguments(config, CSharpCompiler.getSourceFiles(config));
131121
}
132122

133-
// ----------------------------------------------------------------------
134-
//
135-
// ----------------------------------------------------------------------
136-
137-
private Map<String, String> getCompilerArguments(CompilerConfiguration config) {
138-
if (compilerArguments != null) {
139-
return compilerArguments;
140-
}
123+
/**
124+
* Parse compiler arguments and normalize legacy colon-separated format.
125+
* Converts arguments like "-main:MyClass" (stored as key with null value)
126+
* into proper key-value pairs: "-main" -> "MyClass".
127+
*
128+
* @param config the compiler configuration
129+
* @return normalized map of compiler arguments
130+
*/
131+
Map<String, String> getCompilerArguments(CompilerConfiguration config) {
132+
Map<String, String> customArgs = config.getCustomCompilerArgumentsAsMap();
133+
Map<String, String> normalizedArgs = new HashMap<>();
141134

142-
compilerArguments = config.getCustomCompilerArgumentsAsMap();
135+
for (Map.Entry<String, String> entry : customArgs.entrySet()) {
136+
String key = entry.getKey();
137+
String value = entry.getValue();
143138

144-
Iterator<String> i = compilerArguments.keySet().iterator();
139+
// Handle legacy format: "-main:MyClass" stored as key with null value
140+
if (value == null && key.contains(":")) {
141+
int colonIndex = key.indexOf(':');
142+
String actualKey = key.substring(0, colonIndex);
143+
String actualValue = key.substring(colonIndex + 1);
144+
normalizedArgs.put(actualKey, actualValue);
145145

146-
while (i.hasNext()) {
147-
String orig = i.next();
148-
String v = compilerArguments.get(orig);
149-
if (orig.contains(":") && v == null) {
150-
String[] arr = orig.split(":");
151-
i.remove();
152-
String k = arr[0];
153-
v = arr[1];
154-
compilerArguments.put(k, v);
155146
if (config.isDebug()) {
156-
System.out.println("transforming argument from " + orig + " to " + k + " = [" + v + "]");
147+
System.out.println("Normalized argument '" + key + "' to key='" + actualKey + "', value='"
148+
+ actualValue + "'");
157149
}
150+
} else {
151+
normalizedArgs.put(key, value);
158152
}
159153
}
160154

161-
config.setCustomCompilerArgumentsAsMap(compilerArguments);
162-
163-
return compilerArguments;
155+
return normalizedArgs;
164156
}
165157

166158
private String findExecutable(CompilerConfiguration config) {
@@ -179,45 +171,53 @@ private String findExecutable(CompilerConfiguration config) {
179171

180172
/*
181173
$ mcs --help
182-
Mono C# compiler, (C) 2001 - 2003 Ximian, Inc.
174+
Turbo C# compiler, Copyright 2001-2011 Novell, Inc., 2011-2016 Xamarin, Inc, 2016-2017 Microsoft Corp
183175
mcs [options] source-files
184-
--about About the Mono C# compiler
185-
-addmodule:MODULE Adds the module to the generated assembly
186-
-checked[+|-] Set default context to checked
187-
-codepage:ID Sets code page to the one in ID (number, utf8, reset)
188-
-clscheck[+|-] Disables CLS Compliance verifications
189-
-define:S1[;S2] Defines one or more symbols (short: /d:)
190-
-debug[+|-], -g Generate debugging information
191-
-delaysign[+|-] Only insert the public key into the assembly (no signing)
192-
-doc:FILE XML Documentation file to generate
193-
-keycontainer:NAME The key pair container used to strongname the assembly
194-
-keyfile:FILE The strongname key file used to strongname the assembly
195-
-langversion:TEXT Specifies language version modes: ISO-1 or Default
196-
-lib:PATH1,PATH2 Adds the paths to the assembly link path
197-
-main:class Specified the class that contains the entry point
198-
-noconfig[+|-] Disables implicit references to assemblies
199-
-nostdlib[+|-] Does not load core libraries
200-
-nowarn:W1[,W2] Disables one or more warnings
201-
-optimize[+|-] Enables code optimalizations
202-
-out:FNAME Specifies output file
203-
-pkg:P1[,Pn] References packages P1..Pn
204-
-recurse:SPEC Recursively compiles the files in SPEC ([dir]/file)
205-
-reference:ASS References the specified assembly (-r:ASS)
206-
-target:KIND Specifies the target (KIND is one of: exe, winexe,
207-
library, module), (short: /t:)
208-
-unsafe[+|-] Allows unsafe code
209-
-warnaserror[+|-] Treat warnings as errors
210-
-warn:LEVEL Sets warning level (the highest is 4, the default is 2)
211-
-help2 Show other help flags
176+
--about About the Mono C# compiler
177+
-addmodule:M1[,Mn] Adds the module to the generated assembly
178+
-checked[+|-] Sets default aritmetic overflow context
179+
-clscheck[+|-] Disables CLS Compliance verifications
180+
-codepage:ID Sets code page to the one in ID (number, utf8, reset)
181+
-define:S1[;S2] Defines one or more conditional symbols (short: -d)
182+
-debug[+|-], -g Generate debugging information
183+
-delaysign[+|-] Only insert the public key into the assembly (no signing)
184+
-doc:FILE Process documentation comments to XML file
185+
-fullpaths Any issued error or warning uses absolute file path
186+
-help Lists all compiler options (short: -?)
187+
-keycontainer:NAME The key pair container used to sign the output assembly
188+
-keyfile:FILE The key file used to strongname the ouput assembly
189+
-langversion:TEXT Specifies language version: ISO-1, ISO-2, 3, 4, 5, 6, Default or Experimental
190+
-lib:PATH1[,PATHn] Specifies the location of referenced assemblies
191+
-main:CLASS Specifies the class with the Main method (short: -m)
192+
-noconfig Disables implicitly referenced assemblies
193+
-nostdlib[+|-] Does not reference mscorlib.dll library
194+
-nowarn:W1[,Wn] Suppress one or more compiler warnings
195+
-optimize[+|-] Enables advanced compiler optimizations (short: -o)
196+
-out:FILE Specifies output assembly name
197+
-pathmap:K=V[,Kn=Vn] Sets a mapping for source path names used in generated output
198+
-pkg:P1[,Pn] References packages P1..Pn
199+
-platform:ARCH Specifies the target platform of the output assembly
200+
ARCH can be one of: anycpu, anycpu32bitpreferred, arm,
201+
x86, x64 or itanium. The default is anycpu.
202+
-recurse:SPEC Recursively compiles files according to SPEC pattern
203+
-reference:A1[,An] Imports metadata from the specified assembly (short: -r)
204+
-reference:ALIAS=A Imports metadata using specified extern alias (short: -r)
205+
-sdk:VERSION Specifies SDK version of referenced assemblies
206+
VERSION can be one of: 2, 4, 4.5 (default) or a custom value
207+
-target:KIND Specifies the format of the output assembly (short: -t)
208+
KIND can be one of: exe, winexe, library, module
209+
-unsafe[+|-] Allows to compile code which uses unsafe keyword
210+
-warnaserror[+|-] Treats all warnings as errors
211+
-warnaserror[+|-]:W1[,Wn] Treats one or more compiler warnings as errors
212+
-warn:0-4 Sets warning level, the default is 4 (short -w:)
213+
-helpinternal Shows internal and advanced compiler options
212214
213215
Resources:
214-
-linkresource:FILE[,ID] Links FILE as a resource
215-
-resource:FILE[,ID] Embed FILE as a resource
216+
-linkresource:FILE[,ID] Links FILE as a resource (short: -linkres)
217+
-resource:FILE[,ID] Embed FILE as a resource (short: -res)
216218
-win32res:FILE Specifies Win32 resource file (.res)
217219
-win32icon:FILE Use this icon for the output
218220
@file Read response file for more options
219-
220-
Options can be of the form -option or /option
221221
*/
222222

223223
/*
@@ -385,25 +385,14 @@ SHA1 or SHA256 (default).
385385
accessibility errors with what assembly they came from.
386386
*/
387387

388-
private String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles)
389-
throws CompilerException {
388+
String[] buildCompilerArguments(CompilerConfiguration config, String[] sourceFiles) throws CompilerException {
390389
List<String> args = new ArrayList<>();
391390

392-
if (config.isDebug()) {
393-
args.add("/debug+");
394-
} else {
395-
args.add("/debug-");
396-
}
397-
398391
// config.isShowWarnings()
399392
// config.getSourceVersion()
400393
// config.getTargetVersion()
401394
// config.getSourceEncoding()
402395

403-
// ----------------------------------------------------------------------
404-
//
405-
// ----------------------------------------------------------------------
406-
407396
for (String element : config.getClasspathEntries()) {
408397
File f = new File(element);
409398

@@ -433,122 +422,91 @@ private String[] buildCompilerArguments(CompilerConfiguration config, String[] s
433422
}
434423
}
435424

436-
// ----------------------------------------------------------------------
437-
// Main class
438-
// ----------------------------------------------------------------------
439-
425+
// TODO: include all user compiler arguments and not only some!
440426
Map<String, String> compilerArguments = getCompilerArguments(config);
441427

442428
String mainClass = compilerArguments.get("-main");
443-
444429
if (!StringUtils.isEmpty(mainClass)) {
445430
args.add("/main:" + mainClass);
446431
}
447432

448-
// ----------------------------------------------------------------------
449433
// Xml Doc output
450-
// ----------------------------------------------------------------------
451-
452434
String doc = compilerArguments.get("-doc");
453-
454435
if (!StringUtils.isEmpty(doc)) {
455436
args.add("/doc:"
456437
+ new File(config.getOutputLocation(), config.getOutputFileName() + ".xml").getAbsolutePath());
457438
}
458439

459-
// ----------------------------------------------------------------------
460-
// Nowarn option
461-
// ----------------------------------------------------------------------
440+
// Debug option (full, pdbonly...)
441+
String debug = compilerArguments.get("-debug");
442+
if (!StringUtils.isEmpty(debug)) {
443+
args.add("/debug:" + debug);
444+
}
462445

446+
// Nowarn option (w#1,w#2...)
463447
String nowarn = compilerArguments.get("-nowarn");
464-
465448
if (!StringUtils.isEmpty(nowarn)) {
466449
args.add("/nowarn:" + nowarn);
467450
}
468451

469-
// ----------------------------------------------------------------------
470452
// Out - Override output name, this is required for generating the unit test dll
471-
// ----------------------------------------------------------------------
472-
473453
String out = compilerArguments.get("-out");
474-
475454
if (!StringUtils.isEmpty(out)) {
476455
args.add("/out:" + new File(config.getOutputLocation(), out).getAbsolutePath());
477456
} else {
478457
args.add("/out:" + new File(config.getOutputLocation(), getOutputFile(config)).getAbsolutePath());
479458
}
480459

481-
// ----------------------------------------------------------------------
482460
// Resource File - compile in a resource file into the assembly being created
483-
// ----------------------------------------------------------------------
484461
String resourcefile = compilerArguments.get("-resourcefile");
485-
486462
if (!StringUtils.isEmpty(resourcefile)) {
487463
String resourceTarget = compilerArguments.get("-resourcetarget");
488464
args.add("/res:" + new File(resourcefile).getAbsolutePath() + "," + resourceTarget);
489465
}
490466

491-
// ----------------------------------------------------------------------
492-
// Target - type of assembly to produce, lib,exe,winexe etc...
493-
// ----------------------------------------------------------------------
494-
467+
// Target - type of assembly to produce: library,exe,winexe...
495468
String target = compilerArguments.get("-target");
496-
497469
if (StringUtils.isEmpty(target)) {
498470
args.add("/target:library");
499471
} else {
500472
args.add("/target:" + target);
501473
}
502474

503-
// ----------------------------------------------------------------------
504475
// remove MS logo from output (not applicable for mono)
505-
// ----------------------------------------------------------------------
506476
String nologo = compilerArguments.get("-nologo");
507-
508-
if (!StringUtils.isEmpty(nologo)) {
477+
if (!StringUtils.isEmpty(nologo) && !"false".equalsIgnoreCase(nologo)) {
509478
args.add("/nologo");
510479
}
511480

512-
// ----------------------------------------------------------------------
513481
// Unsafe option
514-
// ----------------------------------------------------------------------
515482
String unsafe = compilerArguments.get("-unsafe");
516-
517-
if (!StringUtils.isEmpty(unsafe) && unsafe.equals("true")) {
483+
if (!StringUtils.isEmpty(unsafe) && "true".equalsIgnoreCase(unsafe)) {
518484
args.add("/unsafe");
519485
}
520486

521-
// ----------------------------------------------------------------------
522487
// PreferredUILang option
523-
// ----------------------------------------------------------------------
524488
String preferreduilang = compilerArguments.get("-preferreduilang");
525-
526489
if (!StringUtils.isEmpty(preferreduilang)) {
527490
args.add("/preferreduilang:" + preferreduilang);
528491
}
529492

530-
// ----------------------------------------------------------------------
531493
// Utf8Output option
532-
// ----------------------------------------------------------------------
533494
String utf8output = compilerArguments.get("-utf8output");
534-
535-
if (!StringUtils.isEmpty(utf8output)) {
536-
args.add("/utf8output:");
495+
if (!StringUtils.isEmpty(utf8output) && !"false".equals(utf8output)) {
496+
args.add("/utf8output");
537497
}
538498

539-
// ----------------------------------------------------------------------
540499
// add any resource files
541-
// ----------------------------------------------------------------------
542500
this.addResourceArgs(config, args);
543501

544-
// ----------------------------------------------------------------------
545502
// add source files
546-
// ----------------------------------------------------------------------
547-
for (String sourceFile : sourceFiles) {
548-
args.add(sourceFile);
503+
Collections.addAll(args, sourceFiles);
504+
505+
if (config.isDebug()) {
506+
System.out.println("built compiler arguments:" + args);
549507
}
550508

551-
return args.toArray(new String[args.size()]);
509+
return args.toArray(new String[0]);
552510
}
553511

554512
private void addResourceArgs(CompilerConfiguration config, List<String> args) {
@@ -560,7 +518,7 @@ private void addResourceArgs(CompilerConfiguration config, List<String> args) {
560518
scanner.addDefaultExcludes();
561519
scanner.scan();
562520

563-
List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
521+
String[] includedFiles = scanner.getIncludedFiles();
564522
for (String name : includedFiles) {
565523
File filteredResource = new File(filteredResourceDir, name);
566524
String assemblyResourceName = this.convertNameToAssemblyResourceName(name);
@@ -585,7 +543,7 @@ private File findResourceDir(CompilerConfiguration config) {
585543
if (tempResourcesDirAsString != null) {
586544
filteredResourceDir = new File(tempResourcesDirAsString);
587545
if (config.isDebug()) {
588-
System.out.println("Found resourceDir at: " + filteredResourceDir.toString());
546+
System.out.println("Found resourceDir at: " + filteredResourceDir);
589547
}
590548
} else {
591549
if (config.isDebug()) {

0 commit comments

Comments
 (0)